2017-11-09 18:02 GMT+01:00 Raffaello Giulietti < raffaello.giulietti@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)