[squeak-dev] The joys (or not) of floating point numbers

Nicolas Cellier nicolas.cellier.aka.nice at gmail.com
Tue Mar 26 21:11:41 UTC 2013


2013/3/26 Louis LaBrunda <Lou at keystone-software.com>:
> Hi Nicolas,
>
> snip...
>
>>> In VA Smalltalk:
>>>
>>> 0.995 = 0.995s3 => true
>>> 0.995 < 0.995s3 => false
>
>>This is a default st80 behavior which converts minimumGenerality
>>number to maximumGenerality.
>
> In VA when one converts 0.995 to decimal, one gets 0.995s15, which when
> compared to 0.995s3 is equal.  And when 0.995s3 is converted to float that
> float compares equal to 0.995.
>
>>Maybe some of these conventions are hardwired in VM, I don't know, but
>>in other dialects it's handled at image side.
>
>>The idea was this one: if you perform an operation between an inexact
>>value and an exact value, the result is inexact.
>>So Float + * / - Integer,Fraction,ScaledDecimal will result into a Float.
>>Thus Float got a higher generality.
>
> This would imply that one should convert the ScaledDecimal to a Float
> before the compare.  But as above that would not change things (I think
> maybe because VA uses 8 byte floats).
>
> This is from Squeak:
>
> 0.995s3 asFloat
>  0.995
>
> 0.995s3 asFloat = 0.995
>  true
>
> 0.995 asScaledDecimal
>  0.99499999s8
>
> (995 / 1000) asScaledDecimal
>  0.995s3
>
> 0.995 asTrueFraction asScaledDecimal
>  0.99499999999999999555910790149937383830547332763671875s53
>
> (0.995 * 1000000000) asScaledDecimal / 1000000000
>  0.99500000s8
>

Yep, but (0.995 * 1000000000) is inexact...
You can check that (0.995 * 1000000000) ~~ (0.995 asFraction * 1000000000)

You cannot rely on results of any such inexact calculus, or you'll be
building on sand.

> 0.995 * 1000000000
>  9.95e8
>
> Sorry, but I have run out of time to play at the moment.  So, I will just
> throw a thought out there.  I think there may be a problem with
> asTrueFraction.  Which if implemented differently might not make 0.995 <
> 0.995s3.
>

Well I doubt, but I'm all ears ;)

(0.995 asFraction storeStringBase: 2)
-> '(2r11111110101110000101000111101011100001010001111010111/2r100000000000000000000000000000000000000000000000000000)'.

(0.995 successor) asFraction storeStringBase: 2
-> '(2r11111110101110000101000111101011100001010001111011/2r100000000000000000000000000000000000000000000000000)'
-> '(2r11111110101110000101000111101011100001010001111011000/2r100000000000000000000000000000000000000000000000000000)'.

(0.995 predecessor) asFraction storeStringBase: 2
-> '(2r1111111010111000010100011110101110000101000111101011/2r10000000000000000000000000000000000000000000000000000)'
-> '(2r11111110101110000101000111101011100001010001111010110/2r100000000000000000000000000000000000000000000000000000)'.

0.995 ulp asFraction storeStringBase: 2
-> '(2r1/2r100000000000000000000000000000000000000000000000000000)'.

(995/1000 - 0.995 asFraction) / 0.995 ulp
-> 0.04.

(995/1000 - 0.995 successor asFraction) / 0.995 ulp
->  -0.96.

(995/1000 - 0.995 successor asFraction) / 0.995 ulp
->  1.04.

and numerator/denominator have this property:

2r11111110101110000101000111101011100001010001111010111 highBit
-> 53.
2r100000000000000000000000000000000000000000000000000000 highBit
-> 54.

So, 53 bits of significand, a power of two on denominator, that sounds
like a correct Float, slightly smaller than 1.
Next significand and previous significand are both further of
(995/1000) than 0.995 is.
0.995 is closest Float to 995/1000 and is smaller than 995/1000, no doubt.

Nicolas

>>The idea behind Squeak change is that every Float has an exact value
>>(asTrueFraction).
>>Since we have only two possible answers true/false for comparison, and
>>no maybe or dontKnow, it makes sense to compare the exact value.
>>This reduces the number of paradoxal equalities
>>
>>| a b c |
>>a := 1 << Float precision.
>>b := a + 1.
>>c := a asFloat.
>>{
>>c = b.
>>c = a.
>>a = b.
>>}
>>
>>In VW and VA, the first two are true, the last is false, which
>>suggests that = is not an equivalence relation.
>>In Squeak, only the second one is true.
>>
>>Same with inequalities, we expect (a < b) & (a = c) ==> (c < b) etc...
>>This is still true in Squeak, not in VA/VW/st80.
>>I think gst and Dolphin and maybe stx adopted Squeak behavior (to be confirmed).
>>
>>
>>> I think in VA Smalltalk there is some VM magic going on that makes the
>>> above work the way it does.  The #= and #< of Float are implemented in a
>>> primitive.  I guess when converting the '0.995' string to a float a little
>>> is lost and that would make it less than 0.995s3 but there is a lot of code
>>> floating around (sorry about the puns) to make floats look better.  In that
>>> case I would think people would want (0.995 printShowingDecimalPlaces: 2)
>>> to show '1.00' and not '0.99'.
>>>
>>
>>No, people should not rely on such behavior with Floats, because
>>sooner than later they will be bitten.
>>We cannot cheat very long with this kind of assumptions.
>>My POV is that it is better to educate about false expectations with
>>Float, and that's the main benefit of (1/10) ~= 0.1.
>>
>>I would add that such print policy is not the behaviour of every other
>>language I know of.
>>
>>printf( "%.2f",0.995)
>>-> 0.99
>>
>>Because libm are carefully written nowdays and other languages
>>libraries are either built over libm or much more careful than
>>Smalltalk were (that mostly means more recent).
>>
>>> Anyway, we should try not to use floats without a very, very good reason.
>>> Like we have to send them outside of Smalltalk or we really need the speed
>>> or decimals and fractions take up too much memory.  But then we must live
>>> with their inaccuracies and display mess.
>>>
>>
>>Good advice, let's put those expectations on decimal fractions
>>(ScaledDecimal/FixedPoint) or general Fraction.
>>
>>> I have an untested theory that fractions can be close in speed to floats
>>> because divisions (that are expensive) can be pushed to the end of a
>>> computation because with fractions they are multiplies.
>>>
>>> Lou
>>
>>Why not, but huge numerators and denominators are not cheap compared
>>to Float operations.
>>OK reducing the fraction is the expensive part, but how does the cost
>>grow versus the length of operands?
>>
>>Also some geometric operations are not even possible on Q (hypot) so
>>you might soon need AlgebraicNumber.
>>And Smalltalk is also about graphics and geometry.
>>
>>Nicolas
>>
>>> -----------------------------------------------------------
>>> Louis LaBrunda
>>> Keystone Software Corp.
>>> SkypeMe callto://PhotonDemon
>>> mailto:Lou at Keystone-Software.com http://www.Keystone-Software.com
>>>
>>>
>>
> -----------------------------------------------------------
> Louis LaBrunda
> Keystone Software Corp.
> SkypeMe callto://PhotonDemon
> mailto:Lou at Keystone-Software.com http://www.Keystone-Software.com
>
>


More information about the Squeak-dev mailing list