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

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


2013/3/26 Nicolas Cellier <nicolas.cellier.aka.nice at gmail.com>:
> 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)
>

Oops, I mean (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