[Pharo-dev] float & fraction equality bug

Nicolas Cellier nicolas.cellier.aka.nice at gmail.com
Thu Nov 9 13:04:51 EST 2017

2017-11-09 18:02 GMT+01:00 Raffaello Giulietti <
raffaello.giulietti at lifeware.ch>:

> On 2017-11-09 15:50, Nicolas Cellier wrote:
>> This is out of context.
>> There is no such thing as Fraction type covered by IEEE 754 standard.
> Yes, I agree. But we should still strive to model arithmetic embracing the
> principle of least surprise. That's why in every arithmetic system I'm
> aware of (with the exception of very old CPUs dating back several decades),
> for finite x, y the
>     x = y if and only if x - y = 0
> property holds.
> Let's put it in another perspective: what's the usefulness of having
>     x = y
> evaluate to false just to discover that
>     x - y
> evaluates to 0, or the other way round?
> Anyway relying upon Float equality should allways be subject to extreme
>> caution and examination
>> For example, what do you expect with plain old arithmetic in mind:
>>      a := 0.1.
>>      b := 0.3 - 0.2.
>>      a = b
>> This will lead to (a - b) reciprocal = 3.602879701896397e16
>> If it is in a Graphics context, I'm not sure that it's the expected
>> scale...
> a = b evaluates to false in this example, so no wonder (a - b) evaluates
> to a big number.
Writing a = b with floating point is rarely a good idea, so asking about
the context which could justify such approach makes sense IMO.

But the example is not plain old arithmetic.
> Here, 0.1, 0.2, 0.3 are just a shorthands to say "the Floats closest to
> 0.1, 0.2, 0.3" (if implemented correctly, like in Pharo as it seems). Every
> user of Floats should be fully aware of the implicit loss of precision that
> using Floats entails.
Yes, it makes perfect sense!
But precisely because you are aware that 0.1e0 is "the Float closest to
0.1" and not exactly 1/10, you should then not be surprised that they are
not equal.

> So, using Floats to represent decimal numbers is the real culprit in this
> example, not the underlying Float arithmetic, which is very well defined
> from a mathematical point of view. In other words, using Floats to emulate
> decimal arithmetic will frustrate anybody because Floats work with limited
> precision binary arithmetic. Users wanting to engage in decimal arithmetic
> should simply not use Floats. (That's the reason for the addition of
> limited precision decimal arithmetic and numbers in the IEEE 754-2008
> standard.)
> That said, this does not mean we should give up useful properties like the
> one discussed above. Since we *can* ensure this property, we also should,
> in the spirit of the principle of least surprise.
> What's problematic in Pharo is that comparison works in one way while
> subtraction works in another way but, mathematically, these operations are
> essentially the same. So let's be consistent.
I agree that following assertion hold:
    self assert: a ~= b & a isFloat & b isFloat & a isFinite & b isFinite
==> (a - b) isZero not

But (1/10) is not a Float and there is no Float that can represent it
exactly, so you can simply not apply the rules of FloatingPoint on it.

When you write (1/10) - 0.1, you implicitely perform (1/10) asFloat - 0.1.
It is the rounding operation asFloat that made the operation inexact, so
it's no more surprising than other floating point common sense

In the case of mixed-mode Float/Fraction operations, I personally prefer
> reducing the Fraction to a Float because other commercial Smalltalk
> implementations do so, so there would be less pain porting code to Pharo,
> perhaps attracting more Smalltalkers to Pharo.
> Mixed arithmetic is problematic, and from my experience mostly happens in
graphics in Smalltalk.

If ever I would change something according to this principle (but I'm not
convinced it's necessary, it might lead to other strange side effects),
maybe it would be how mixed arithmetic is performed...
Something like exact difference like Martin suggested, then converting to
nearest Float because result is inexact:
    ((1/10) - 0.1 asFraction) asFloat

This way, you would have a less surprising result in most cases.
But i could craft a fraction such that the difference underflows, and the
assertion a ~= b ==> (a - b) isZero not would still not hold.
Is it really worth it?
Will it be adopted in other dialects?

> But the main point here, I repeat myself, is to be consistent and to have
> as much regularity as intrinsically possible.
I think we have as much as possible already.
Non equality resolve more surprising behavior than it creates.
It makes the implementation more mathematically consistent (understand
preserving more properties).
Tell me how you are going to sort these 3 numbers:

{1.0 . 1<<60+1/(1<<60).  1<<61+1/(1<<61)} sort.

tell me the expectation of:

{1.0 . 1<<60+1/(1<<60). 1<<61+1/(1<<61)} asSet size.

tell me why = is not a relation of equivalence anymore (not associative)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.pharo.org/pipermail/pharo-dev_lists.pharo.org/attachments/20171109/31ae252f/attachment-0002.html>

More information about the Pharo-dev mailing list