Hi Folks

Eric and I have just posted a new script to the REBOL script library. You
can download it directly from:

http://www.rebol.org/math/decimal.r

The purpose of this package of functions is to improve REBOL'S support for
floating-point mathematics. It provides many of the capabilities present in
languages like C and FORTRAN, but which are currently unavailable in REBOL,
such as native binary I/O.

The package is the result of a collaboration between Eric Long, myself, and
Gerald Goertzel. Comments, questions and suggestions are welcome. We are
especially interested in whether these functions (which have only been
tested with the Windows version of REBOL) produce the same results on other
platforms and non-Intel CPU's. Please address any comments to the REBOL
mail-list or send them directly to me at [EMAIL PROTECTED]

The package is heavily documented with comments and examples.

The functionality is encapsulated in just three objects: REAL, IEEE, and
EMPIRICAL. These are the only names that are added to the global context.

The REAL object---------------------

This object contains many functions for the manipulation and examination of
REBOL decimal values.

Among other things, these functions provide full support for native binary
floating-point file IO, compatible with C, FORTRAN, etc.

REBOL decimal and money values may also be saved transparently in a native
binary hex-string and loaded without incurring roundoff error.

These functions are necessary because the standard REBOL representation of
decimal values may round off one or two significant digits, and the rounding
of money values is an even greater source of error.

Outline of functions in the REAL object---------------------

Main Interface Functions
FORM           Returns precise decimal representation (requires format.r).
SHOW           Returns a string showing the bits in the IEEE representation.
TO-NATIVE      Converts a decimal value into a native IEEE binary
FROM-NATIVE    Converts an 8-byte native binary into a decimal value.
SAVE           Saves a value with all decimal values converted.
LOAD           Loads a value and restores 8-byte binaries to decimals.
WRITE          Writes a block of decimal values to a file in native form.
READ           Reads a file containing a series of decimal values expressed
               in native form.

Helper Functions
SPLIT          Returns a block with the three components of the IEEE
               double floating point representation of a decimal value.
CONVERT        Converts all decimal values to native binaries, leaves other
               values untouched.
RESTORE        Restores 8-byte binaries to decimal values.
TO-BIN         Returns a binary string representation or a numeric value.
FROM-BIN       Returns the integer represented by string of 1's and 0's.
TO-MATRIX      Converts a flat block into nested blocks of any depth.

Most of the functions come in pairs:
    SAVE <--> LOAD
 WRITE <--> READ
 TO-NATIVE <--> FROM-NATIVE
 CONVERT <--> RESTORE
 TO-BIN <--> FROM/BIN

These pairs have the property of reversing exactly (i.e., FROM-BIN TO-BIN X
yields X).

The IEEE and EMPIRICAL objects---------------

These provide several utility functions useful for more in-depth exploration
of the details of floating-point numbers in REBOL and testing the REBOL
comparison functions. The two objects provide similar functionality but
differ in implementation. In the IEEE object all of the values are
calculated according to the IEEE754 standard for double-precision
floating-point. In the EMPIRICAL object, the values are calculated using an
iterative empirical method (similar to root-finding). Most of the
calculations in both are done in binary.

Outline of functions in the IEEE and ENPIRICAL objects------------

REBTEST   A function that tests the sensitivity of the REBOL EQUAL?
          comparison function.
GET-NEXT  A function that makes the minimum increment to a decimal value.
GET-LAST  A function that makes the maximum decrement to a decimal value.
MAX-REAL  The largest decimal value.
MIN-NORM  The smallest positive normalized decimal. (IEEE only)
MIN-REAL  The smallest positive decimal value.
EPS       The minimum increment to 1 (smallest x such that (x+1)>1).

REBTEST illustrates some flaws in the implementation of the REBOL comparison
functions (as of version 2.2), but note that it is easy to get exact
comparisons by using ZERO? NEGATIVE? and POSITIVE? to test the results of
subtraction.

Using FORMAT

Several of the functions can optionally use the full-precision formatting
available in Eric's FORMAT function. These functions are:

        real/form
        real/show/decimal
        IEEE/rebtest
        empirical/rebtest

You can download FORMAT at

http://www.rebol.org/math/format.r

If you wish to use it in conjunction with DECIMAL, you should load format.r
before loading decimal.r.

Note: None of the examples given in this post make use of FORMAT.

There is way too much in this package to describe in this post, so I will
just present a few examples below. If there is interest, I am willing to
write a few short tutorial posts about floating-point in REBOL.

Have fun exploring REBOL numbers:-)

Larry



EXAMPLES ================================

REAL/SPLIT calculates the three components of the IEEE representation of a
double floating point value. These are the actual values used by the CPU
in numerical calculations.

>> set [sign exponent fraction] real/split pi
== [0 1024 2.57063812465794E+15]

>> (-1 ** sign) * (2 ** (exponent - 1023)) * (1 + (fraction * (2 ** -52)))
== 3.14159265358979

Numbers smaller in absolute magnitude than 2 ** -1022 (denormals, which have
an exponent component of zero) use a different formula:

>> set [sign exponent fraction] real/split probe 2 ** -1030
8.69169475979376E-311
== [0 0 17592186044416]

>> (-1 ** sign) * (2 ** -1074) * fraction
== 8.69169475979376E-311


REAL/SHOW returns a bit-string which shows the internal IEEE754 double
format. We have separated the 3 components with spaces for clarity. The
format is 64 bits wide with the leading bit being a sign bit (0 pos and
1 neg). The next 11 bits are the base-2 biased exponent. It is biased by
adding 1023 to the actual exponent so that internally all exponents are
non-negative. The remaining 52 bits are the fractional part of the binary
mantissa.

There are two types of doubles: normalized and denormalized. The
normalized values cover the range from 2 ** -1022 up to +Inf. The
denormalized values are equally spaced between 0 and 2 ** -1022 and have
an exponent of 0. The normalized values have an implicit binary point and
leading bit of 1 (i.e., the mantissa 010... represents 1.010...).

>> real/show ieee/min-real
== {0 00000000000 0000000000000000000000000000000000000000000000000001}

>> real/show 0
== {0 00000000000 0000000000000000000000000000000000000000000000000000}

>> real/show negate ieee/min-real
== {1 00000000000 0000000000000000000000000000000000000000000000000001}

>> real/show ieee/get-next 1
== {0 01111111111 0000000000000000000000000000000000000000000000000001}

>> real/show 1
== {0 01111111111 0000000000000000000000000000000000000000000000000000}

>> real/show ieee/get-last 1
== {0 01111111110 1111111111111111111111111111111111111111111111111111}

>> real/show ieee/max-real
== {0 11111111110 1111111111111111111111111111111111111111111111111111}


REAL/TO-NATIVE returns the binary representation of a double. On some
machines(Intel and Motorola 68xxx), the 8 bytes are stored in RAM and on
disk with the bytes reversed (i.e., the least significant byte is the
first one encountered in memory). Reading in a file of doubles of unknown
format may require trying both reversed and and unreversed formats.

>> real/to-native pi
== #{400921FB54442D18}

>> real/to-native/rev pi
== #{182D4454FB210940}            ; configuration used in PC's

>> real/from-native real/to-native pi
== 3.14159265358979

>> pi - real/from-native real/to-native pi
== 0                              ; converts back to exact value


REAL/WRITE and REAL/READ are meant for writing and reading decimal values in
native binary format. REAL/WRITE accepts a single numerical value or a block
of numerical values that may be nested to any depth, but the return value of
REAL/READ is always a flat block.

>> b1: [] for x 1 16 1 [append b1 square-root x]
== [1 1.4142135623731 1.73205080756888 2 2.23606797749979 ...

>> real/write %test.bin b1
16 decimal values written

>> bb1: real/read %test.bin
== [1 1.4142135623731 1.73205080756888 2 2.23606797749979 ...

>> b1/7 - probe bb1/7
2.64575131106459                ; square root of 7
== 0                            ; is restored exactly

>> b2: real/to-matrix b1 4      ; convert to 4x4 matrix
== [[1 1.4142135623731 1.73205080756888 2] [2.23606797749979 ...

>> real/write %test.bin b2
16 decimal values written       ; the same values are written,

>> bb2: real/read %test.bin     ; and read back into a flat block
== [1 1.4142135623731 1.73205080756888 2 2.23606797749979 ...


REAL/SAVE and REAL/LOAD are meant to be completely compatible with the
standard functions SAVE and LOAD, except that there will be no roundoff
error from saving and loading decimal or money values.

>> amount: [100 * $1.504]
== [100 * $1.50]

>> save %test-x.dat amount
>> do load %test-x.dat
== $150.00                             ; large round-off error

>> real/save %test-y.dat amount
>> do real/load %test-y.dat
== $150.40                             ; no error


IEEE/REBTEST makes ten successive minimum increments and decrements to the
target value, and tests whether EQUAL? returns TRUE when comparing
the results to the original value. In all cases EQUAL? should return
false. This example is done without having FORMAT loaded.

Let's use 2 ** 53 as the target. All positive integers from 1 to 2 ** 53
are exactly represented as binary doubles. All doubles greater than 2 ** 52
are integers. The doubles from 2 ** 52 thru 2 ** 53 are consecutive
integers.

The first column shows the result of REBOL's test for equality of the
target to (target + increment). The 2nd column shows the REBOL display
value and the 3rd column shows the increment.

>> ieee/rebtest 2 ** 53             ; the exact value is 9007199254740992
target 9.00719925474099E+15         ; REBOL display rounds to 15 sig. digits
positive increments
t=t+i target+increment increment
true 9.00719925474099E+15 2         ; positive increments are of size 2
true 9.007199254741E+15 4           ; REBOL display value changes
true 9.007199254741E+15 6           ; REBOL equality test is wrong
true 9.007199254741E+15 8
true 9.007199254741E+15 10
true 9.007199254741E+15 12
true 9.00719925474101E+15 14        ; REBOL display changes again
true 9.00719925474101E+15 16        ; but equality test is still wrong
false 9.00719925474101E+15 18       ; equality test correct once we add 18
false 9.00719925474101E+15 20
negative increments
t=t+i target+increment increment
false 9.00719925474099E+15 -1       ; negative increments are of size 1
false 9.00719925474099E+15 -2
false 9.00719925474099E+15 -3
false 9.00719925474099E+15 -4
false 9.00719925474099E+15 -5
false 9.00719925474099E+15 -6       ; REBOL equality test OK for all inc.
false 9.00719925474099E+15 -7
false 9.00719925474098E+15 -8       ; REBOL display changes
false 9.00719925474098E+15 -9
false 9.00719925474098E+15 -10

Other interesting targets are: 0, 1, ieee/max-real, ieee/min-real, 2 ** 52,
and ieee/min-norm.

>> empirical/rebtest 2 ** 53
Increments: Smallest NOT EQUAL? Factor
Positive: 2 18 9                 ; EQUAL? inc 9 times larger than true
Negative: -1 -1 1

Reply via email to