-
Notifications
You must be signed in to change notification settings - Fork 0
UsingArbitraryPrecisionFloat
To play with ArbitraryPrecisionFloat, an easy way is to follow these lines, copy/paste some code in a Smalltalk workspace and doIt/printIt. The expected results are indicated in bold.
The first thing is to create an ArbitraryPrecisionFloat. This is generally obtained by converting another Smalltalk Number.
1 asArbitraryPrecisionFloatNumBits: 200.
11/3 asArbitraryPrecisionFloatNumBits: 200.
Like another Float, an ArbitraryPrecisionFloat is inexact, but you can control the precision:
((1/3) asFloatD asFraction - (1/3)) asFloatD.
-> -1.850371707708594e-17
((1/3 asArbitraryPrecisionFloatNumBits: 200) asFraction - (1/3)) asFloatD.
-> 1.037169212976857e-61
Once you created an ArbitraryPrecisionFloat, you just act on it as with any other Smalltalk Number.
| a b |
a := 1/3 asArbitraryPrecisionFloatNumBits: 100.
b := 2 asArbitraryPrecisionFloatNumBits: 100.
^b sqrt * a.
-> (ArbitraryPrecisionFloat readFrom: '0.471404520791031682933896241403' readStream numBits: 100)
You can mix inexact and exact operands, in which case the result is inexact:
(1 asArbitraryPrecisionFloatNumBits: 16) + (1/3).
-> (ArbitraryPrecisionFloat readFrom: '1.33334' readStream numBits: 16)
You can mix precisions, in which case the operands are converted to higher precision:
(1/3 asArbitraryPrecisionFloatNumBits: 16) - (1/3 asArbitraryPrecisionFloatNumBits: 24).
-> (ArbitraryPrecisionFloat readFrom: '2.53e-6' readStream numBits: 24)
What you see here, is again the result of inexact conversion of the Fraction 1/3 into a Float in base 2.
The rounding error of course depends on the precision.
You can play with the digits of pi, by just writing this snippet:
(0 asArbitraryPrecisionFloatNumBits: 320) pi.
-> (ArbitraryPrecisionFloat readFrom: '3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117' readStream numBits: 320)
Note that 10 binary digits are roughly equivalent to 3 decimal digits (2^10=1024), thus we effectively get around 96 decimal digits with 320 bits.
Also remember that the last digit is not exact, it suffers both from the rounding error for pi, and another rounding error for the base2 -> base 10 conversion, thus the last digit(s) might change if you ask for a few more digits.
A requirement for ArbitraryPrecisionFloat mathematical functions is to differ from exact result by no more than half a unit of least precision. This is much stricter than IEEE 754 which has relaxed these requirements (probably to fit existing hardware/software floating point implementations). Also remind that ArbitraryPrecisionFloat can afford to be slow. Your machine optimized Floating points have different trade off - they must be fast. Thus the results of ArbitraryPrecisionFloat may differ from your floating point math library, but don't trust your math library too much.
For example, in Squeak on MacOSX:
7 asFloatD ln storeString.
-> '1.9459101490553135'
(7 asArbitraryPrecisionFloatNumBits: Float precision) ln.
-> (ArbitraryPrecisionFloat readFrom: '1.9459101490553132' readStream numBits: 53)
Though we could expect same results on such a simple case...
The result is confirmed by increasing precision:
(7 asArbitraryPrecisionFloatNumBits: 70) ln.
-> (ArbitraryPrecisionFloat readFrom: '1.945910149055313305106' readStream numBits: 70)
The value rounded to 53 bits is correct:
(7 asArbitraryPrecisionFloatNumBits: 70) ln asFloatD storeString.
-> '1.9459101490553132'
Note, that the last digit is not guaranteed to match correct decimal rounding, the decimal representation is only expected to give back exactly the original value when rounded to base 2, and both 1.9459101490553132 and 1.9459101490553133 do.
The difference is less than 1/2 ulp for the ArbitraryPrecisionFloat result:
((7 asArbitraryPrecisionFloatNumBits: 70) ln - (7 asArbitraryPrecisionFloatNumBits: 53) ln)
/ (7 asFloatD ln ulp).
-> (ArbitraryPrecisionFloat readFrom: '0.32983' readStream numBits: 70)
And more than 1/2 ulp for the FloatD result:
((7 asArbitraryPrecisionFloatNumBits: 70) ln - (7 asFloatD ln)) / (7 asFloatD ln ulp).
-> (ArbitraryPrecisionFloat readFrom: '-0.67017' readStream numBits: 70)
Note that #ulp is a Squeakism meaning unit of least precision, but you might find equivalent messages in other dialects. That's all for this short introduction, let's hope that you'll find a good usage of this class, or at least that these examples will provide educational benefits about floating point arithmetic and inexactness.