float & fraction equality bug
Hi, I just stumbled across this bug related to the equality between fraction and float: https://pharo.fogbugz.com/f/cases/20488/x-y-iff-x-y-0-is-not-preserved-in-Ph... In essence, the problem can be seen that by doing this, you get a ZeroDivide: x := 0.1. y := (1/10). x = y ifFalse: [ 1 / (x - y) ] The issue seems to come from the Float being turned to a Fraction, rather than the Fraction being turned into a Float: Fraction(Number)>>adaptToFloat: rcvr andCompare: selector "If I am involved in comparison with a Float, convert rcvr to a Fraction. This way, no bit is lost and comparison is exact." rcvr isFinite ifFalse: [ selector == #= ifTrue: [^false]. selector == #~= ifTrue: [^true]. rcvr isNaN ifTrue: [^ false]. (selector = #< or: [selector = #'<=']) ifTrue: [^ rcvr positive not]. (selector = #> or: [selector = #'>=']) ifTrue: [^ rcvr positive]. ^self error: 'unknow comparison selector']. ^ rcvr asTrueFraction perform: selector with: self Even if the comment says that the comparison is exact, to me this is a bug because it seems to fail doing that. What do you think? Cheers, Doru -- www.tudorgirba.com www.feenk.com "Problem solving should be focused on describing the problem in a way that makes the solution obvious."
On 11/09/2017 06:15 AM, Tudor Girba wrote:
Hi,
I just stumbled across this bug related to the equality between fraction and float: https://pharo.fogbugz.com/f/cases/20488/x-y-iff-x-y-0-is-not-preserved-in-Ph...
In essence, the problem can be seen that by doing this, you get a ZeroDivide: x := 0.1. y := (1/10). x = y ifFalse: [ 1 / (x - y) ]
The issue seems to come from the Float being turned to a Fraction, rather than the Fraction being turned into a Float:
Fraction(Number)>>adaptToFloat: rcvr andCompare: selector "If I am involved in comparison with a Float, convert rcvr to a Fraction. This way, no bit is lost and comparison is exact."
rcvr isFinite ifFalse: [ selector == #= ifTrue: [^false]. selector == #~= ifTrue: [^true]. rcvr isNaN ifTrue: [^ false]. (selector = #< or: [selector = #'<=']) ifTrue: [^ rcvr positive not]. (selector = #> or: [selector = #'>=']) ifTrue: [^ rcvr positive]. ^self error: 'unknow comparison selector'].
^ *rcvr asTrueFraction perform: selector with: self*
Even if the comment says that the comparison is exact, to me this is a bug because it seems to fail doing that. What do you think?
I think exact comparison is the best thing to do here (even though ANSI says otherwise). And an exact comparison will answer false, because there is no float that is exactly equal to 1/10 -- it is an infinitely repeating decimal in binary. For consistency, though, it might be better to compute x - y by converting the Float to a Fraction rather than the other way around (though this would also contravene ANSI) since Fraction is the more general format (every Float can be represented exactly as a Fraction, but the reverse is not true). Regards, -Martin
2017-11-09 15:34 GMT+01:00 Martin McClure <martin@hand2mouse.com>:
On 11/09/2017 06:15 AM, Tudor Girba wrote:
Hi,
I just stumbled across this bug related to the equality between fraction and float: https://pharo.fogbugz.com/f/cases/20488/x-y-iff-x-y-0-is- not-preserved-in-Pharo
In essence, the problem can be seen that by doing this, you get a ZeroDivide: x := 0.1. y := (1/10). x = y ifFalse: [ 1 / (x - y) ]
The issue seems to come from the Float being turned to a Fraction, rather than the Fraction being turned into a Float:
Fraction(Number)>>adaptToFloat: rcvr andCompare: selector "If I am involved in comparison with a Float, convert rcvr to a Fraction. This way, no bit is lost and comparison is exact."
rcvr isFinite ifFalse: [ selector == #= ifTrue: [^false]. selector == #~= ifTrue: [^true]. rcvr isNaN ifTrue: [^ false]. (selector = #< or: [selector = #'<=']) ifTrue: [^ rcvr positive not]. (selector = #> or: [selector = #'>=']) ifTrue: [^ rcvr positive]. ^self error: 'unknow comparison selector'].
^ *rcvr asTrueFraction perform: selector with: self*
Even if the comment says that the comparison is exact, to me this is a bug because it seems to fail doing that. What do you think?
I think exact comparison is the best thing to do here (even though ANSI says otherwise). And an exact comparison will answer false, because there is no float that is exactly equal to 1/10 -- it is an infinitely repeating decimal in binary.
For consistency, though, it might be better to compute x - y by converting the Float to a Fraction rather than the other way around (though this would also contravene ANSI) since Fraction is the more general format (every Float can be represented exactly as a Fraction, but the reverse is not true).
The POV is that Float are potentially inexact (some quantity rounded to nearest representable Float). While Fraction are exact. If you mix exact quantity with inexact, then the result is inexact (thus Float). It does not hurt that this choice is CPU friendly. Regards,
-Martin
Doru, 1/10 cannot be represented as Float without loss of precision. So even though (1/10) asFloat gives you 0.1, the reverse is not possible. Floating point is a binary (as in base 2 representation), not a decimal (base 10) representation. Consider (1/8) asFloat asTrueFraction. which is reversible. x := 0.125. y := (1/8). x = y ifFalse: [ 1 / (x - y) ]. You can see that a bit in the binary representation view: Sven
On 9 Nov 2017, at 15:15, Tudor Girba <tudor@tudorgirba.com> wrote:
Hi,
I just stumbled across this bug related to the equality between fraction and float: https://pharo.fogbugz.com/f/cases/20488/x-y-iff-x-y-0-is-not-preserved-in-Ph...
In essence, the problem can be seen that by doing this, you get a ZeroDivide: x := 0.1. y := (1/10). x = y ifFalse: [ 1 / (x - y) ]
The issue seems to come from the Float being turned to a Fraction, rather than the Fraction being turned into a Float:
Fraction(Number)>>adaptToFloat: rcvr andCompare: selector "If I am involved in comparison with a Float, convert rcvr to a Fraction. This way, no bit is lost and comparison is exact."
rcvr isFinite ifFalse: [ selector == #= ifTrue: [^false]. selector == #~= ifTrue: [^true]. rcvr isNaN ifTrue: [^ false]. (selector = #< or: [selector = #'<=']) ifTrue: [^ rcvr positive not]. (selector = #> or: [selector = #'>=']) ifTrue: [^ rcvr positive]. ^self error: 'unknow comparison selector'].
^ rcvr asTrueFraction perform: selector with: self
Even if the comment says that the comparison is exact, to me this is a bug because it seems to fail doing that. What do you think?
Cheers, Doru
-- www.tudorgirba.com www.feenk.com
"Problem solving should be focused on describing the problem in a way that makes the solution obvious."
Nope, not a bug. If you use Float, then you have to know that (x -y) isZero and (x = y) are two different things. Example; Float infinity In your case you want to protect against (x-y) isZero, so just do that. 2017-11-09 15:15 GMT+01:00 Tudor Girba <tudor@tudorgirba.com>:
Hi,
I just stumbled across this bug related to the equality between fraction and float: https://pharo.fogbugz.com/f/cases/20488/x-y-iff-x-y-0-is- not-preserved-in-Pharo
In essence, the problem can be seen that by doing this, you get a ZeroDivide: x := 0.1. y := (1/10). x = y ifFalse: [ 1 / (x - y) ]
The issue seems to come from the Float being turned to a Fraction, rather than the Fraction being turned into a Float:
Fraction(Number)>>adaptToFloat: rcvr andCompare: selector "If I am involved in comparison with a Float, convert rcvr to a Fraction. This way, no bit is lost and comparison is exact."
rcvr isFinite ifFalse: [ selector == #= ifTrue: [^false]. selector == #~= ifTrue: [^true]. rcvr isNaN ifTrue: [^ false]. (selector = #< or: [selector = #'<=']) ifTrue: [^ rcvr positive not]. (selector = #> or: [selector = #'>=']) ifTrue: [^ rcvr positive]. ^self error: 'unknow comparison selector'].
^ *rcvr asTrueFraction perform: selector with: self*
Even if the comment says that the comparison is exact, to me this is a bug because it seems to fail doing that. What do you think?
Cheers, Doru
-- www.tudorgirba.com www.feenk.com
"Problem solving should be focused on describing the problem in a way that makes the solution obvious."
According to IEEE 754, the base of Pharo Float, *finite* values shall behave like old plain arithmetic. On 2017-11-09 15:36, Nicolas Cellier wrote:
Nope, not a bug.
If you use Float, then you have to know that (x -y) isZero and (x = y) are two different things. Example; Float infinity
In your case you want to protect against (x-y) isZero, so just do that.
2017-11-09 15:15 GMT+01:00 Tudor Girba <tudor@tudorgirba.com <mailto:tudor@tudorgirba.com>>:
Hi,
I just stumbled across this bug related to the equality between fraction and float: https://pharo.fogbugz.com/f/cases/20488/x-y-iff-x-y-0-is-not-preserved-in-Ph... <https://pharo.fogbugz.com/f/cases/20488/x-y-iff-x-y-0-is-not-preserved-in-Ph...>
In essence, the problem can be seen that by doing this, you get a ZeroDivide: x := 0.1. y := (1/10). x = y ifFalse: [ 1 / (x - y) ]
The issue seems to come from the Float being turned to a Fraction, rather than the Fraction being turned into a Float:
Fraction(Number)>>adaptToFloat: rcvr andCompare: selector "If I am involved in comparison with a Float, convert rcvr to a Fraction. This way, no bit is lost and comparison is exact."
rcvr isFinite ifFalse: [ selector == #= ifTrue: [^false]. selector == #~= ifTrue: [^true]. rcvr isNaN ifTrue: [^ false]. (selector = #< or: [selector = #'<=']) ifTrue: [^ rcvr positive not]. (selector = #> or: [selector = #'>=']) ifTrue: [^ rcvr positive]. ^self error: 'unknow comparison selector'].
^ *rcvr asTrueFraction perform: selector with: self*
Even if the comment says that the comparison is exact, to me this is a bug because it seems to fail doing that. What do you think?
Cheers, Doru
-- www.tudorgirba.com <http://www.tudorgirba.com> www.feenk.com <http://www.feenk.com>
"Problem solving should be focused on describing the problem in a way that makes the solution obvious."
2017-11-09 15:44 GMT+01:00 Raffaello Giulietti < raffaello.giulietti@lifeware.ch>:
According to IEEE 754, the base of Pharo Float, *finite* values shall behave like old plain arithmetic.
This is out of context. There is no such thing as Fraction type covered by IEEE 754 standard.
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...
On 2017-11-09 15:36, Nicolas Cellier wrote:
Nope, not a bug.
If you use Float, then you have to know that (x -y) isZero and (x = y) are two different things. Example; Float infinity
In your case you want to protect against (x-y) isZero, so just do that.
2017-11-09 15:15 GMT+01:00 Tudor Girba <tudor@tudorgirba.com <mailto: tudor@tudorgirba.com>>:
Hi,
I just stumbled across this bug related to the equality between fraction and float: https://pharo.fogbugz.com/f/cases/20488/x-y-iff-x-y-0-is-not -preserved-in-Pharo <https://pharo.fogbugz.com/f/cases/20488/x-y-iff-x-y-0-is-no t-preserved-in-Pharo>
In essence, the problem can be seen that by doing this, you get a ZeroDivide: x := 0.1. y := (1/10). x = y ifFalse: [ 1 / (x - y) ]
The issue seems to come from the Float being turned to a Fraction, rather than the Fraction being turned into a Float:
Fraction(Number)>>adaptToFloat: rcvr andCompare: selector "If I am involved in comparison with a Float, convert rcvr to a Fraction. This way, no bit is lost and comparison is exact."
rcvr isFinite ifFalse: [ selector == #= ifTrue: [^false]. selector == #~= ifTrue: [^true]. rcvr isNaN ifTrue: [^ false]. (selector = #< or: [selector = #'<=']) ifTrue: [^ rcvr positive not]. (selector = #> or: [selector = #'>=']) ifTrue: [^ rcvr positive]. ^self error: 'unknow comparison selector'].
^ *rcvr asTrueFraction perform: selector with: self*
Even if the comment says that the comparison is exact, to me this is a bug because it seems to fail doing that. What do you think?
Cheers, Doru
-- www.tudorgirba.com <http://www.tudorgirba.com> www.feenk.com <http://www.feenk.com>
"Problem solving should be focused on describing the problem in a way that makes the solution obvious."
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. 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. 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. 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. But the main point here, I repeat myself, is to be consistent and to have as much regularity as intrinsically possible.
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)
On 2017-11-09 19:04, Nicolas Cellier wrote:
2017-11-09 18:02 GMT+01:00 Raffaello Giulietti <raffaello.giulietti@lifeware.ch <mailto:raffaello.giulietti@lifeware.ch>>:
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.
Simple contexts, like the one which is the subject of this trail, are the one we should strive at because they are the ones most likely used in day-to-day working. Having useful properties and regularity for simple cases might perhaps cover 99% of the everyday usages (just a dishonestly biased estimate ;-) ) Complex contexts, with heavy arithmetic, are best dealt by numericists when Floats are involved, or with unlimited precision numbers like Fractions by other programmers.
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.
Indeed, I'm not surprised. But then 0.1 - (1/10) shall not evaluate to 0. If it evaluates to 0, then the numbers shall compare as being equal. The surprise lies in the inconsistency between the comparison and the subtraction, not in the isolated operations.
I agree that following assertion hold: Â Â Â self assert: a ~= b & a isFloat & b isFloat & a isFinite & b isFinite ==> (a - b) isZero not
The arrow ==> is bidirectional even for finite Floats: self assert: (a - b) isZero not & a isFloat & b isFloat & a isFinite & b isFinite ==> a ~= b
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
See above my observation about what I consider surprising.
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?
As an alternative, the Float>>asFraction method could return the Fraction with the smallest denominator that would convert to the receiver by the Fraction>>asFloat method. So, 0.1 asFraction would return 1/10 rather than the beefy Fraction it currently returns. To return the beast, one would have to intentionally invoke asExactFraction or something similar. This might cause less surprising behavior. But I have to think more.
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.
A clearly stated rule, consistently applied and known to everybody, helps. In presence of heterogeneous numbers, the rule should state the common denominator, so to say. Hence, the numbers involved in mixed-mode arithmetic are either all converted to one representation or all to the other: whether they are compared or added, subtracted or divided, etc. One rule for mixed-mode conversions, not two.
tell me why = is not a relation of equivalence anymore (not associative)
Ensuring that equality is an equivalence is always a problem when the entities involved are of different nature, like here. This is not a new problem and not inherent in numbers. (Logicians and set theorists would have much to tell.) Even comparing Points and ColoredPoints is problematic, so I have no final answer. In Smalltalk, furthermore, implementing equality makes it necessary to (publicly) expose much more internal details about an object than in other environments.
2017-11-09 20:10 GMT+01:00 Raffaello Giulietti < raffaello.giulietti@lifeware.ch>:
On 2017-11-09 19:04, Nicolas Cellier wrote:
2017-11-09 18:02 GMT+01:00 Raffaello Giulietti < raffaello.giulietti@lifeware.ch <mailto:raffaello.giulietti@lifeware.ch
:
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.
Simple contexts, like the one which is the subject of this trail, are the one we should strive at because they are the ones most likely used in day-to-day working. Having useful properties and regularity for simple cases might perhaps cover 99% of the everyday usages (just a dishonestly biased estimate ;-) )
Complex contexts, with heavy arithmetic, are best dealt by numericists when Floats are involved, or with unlimited precision numbers like Fractions by other programmers.
This differs from my experience. Float strikes in the most simple place were we put false expectation because of a different mental representation
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.
Indeed, I'm not surprised. But then 0.1 - (1/10) shall not evaluate to 0. If it evaluates to 0, then the numbers shall compare as being equal.
The surprise lies in the inconsistency between the comparison and the subtraction, not in the isolated operations.
I agree that following assertion hold: self assert: a ~= b & a isFloat & b isFloat & a isFinite & b isFinite ==> (a - b) isZero not
The arrow ==> is bidirectional even for finite Floats:
self assert: (a - b) isZero not & a isFloat & b isFloat & a isFinite & b isFinite ==> a ~= b
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
See above my observation about what I consider surprising.
As already said, it's a false expectation in the context of mixed
arithmetic.
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?
As an alternative, the Float>>asFraction method could return the Fraction with the smallest denominator that would convert to the receiver by the Fraction>>asFloat method.
So, 0.1 asFraction would return 1/10 rather than the beefy Fraction it currently returns. To return the beast, one would have to intentionally invoke asExactFraction or something similar.
This might cause less surprising behavior. But I have to think more.
No the goal here was to have a non null difference because we need to
preserve inequality for other features. Answering anything but a Float at a high computation price goes against primary purpose of Float (speed, efficiency) If that's what we want, then we shall not use Float in the first place. That's why I don't believe in such proposal The minimal Fraction algorithm is an intersting challenge though. Not sure how to find it... Coming back to a bit of code, we have only minimal decimal (with only powers of 2 & 5 at denominator): {[Float pi asFraction]. [Float pi asMinimalDecimalFraction]} collect: #bench.
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.
A clearly stated rule, consistently applied and known to everybody, helps.
In presence of heterogeneous numbers, the rule should state the common denominator, so to say. Hence, the numbers involved in mixed-mode arithmetic are either all converted to one representation or all to the other: whether they are compared or added, subtracted or divided, etc. One rule for mixed-mode conversions, not two.
Having an economy of rules is allways a good idea.
If you can obtain a consistent system with 1 single rule rather than 2 then go. But if it's at the price of sacrificing higher expectations, that's another matter. Languages that have a simpler arithmetic model, bounded integer, no Fraction, may stick to a single rule. More sofisticated models like you'll find in Lisp and Scheme have exact same logic as Squeak/Pharo. We don't have 2 rules gratuitously as already explained. - Total relation order of non nan values so as to be a good Magnitude citizen imply non equality - Producing Float in case of mixed arithmetic is for practicle purpose: speed (What are those damn Float for otherwise?) it's also justified a posteriori by (exact op: inexact) -> inexact What are you ready to sacrifice/trade?
tell me why = is not a relation of equivalence anymore (not associative)
Ensuring that equality is an equivalence is always a problem when the entities involved are of different nature, like here. This is not a new problem and not inherent in numbers. (Logicians and set theorists would have much to tell.) Even comparing Points and ColoredPoints is problematic, so I have no final answer.
In Smalltalk, furthermore, implementing equality makes it necessary to (publicly) expose much more internal details about an object than in other environments.
Let's focus on Number.
Loosing equivalence is loosing ability to mix Numbers in Set. But not only Numbers... Anything having a Number somewhere in an inst var, like (1/10)@0 and 0.1@0.
2017-11-09 21:55 GMT+01:00 Nicolas Cellier < nicolas.cellier.aka.nice@gmail.com>:
2017-11-09 20:10 GMT+01:00 Raffaello Giulietti < raffaello.giulietti@lifeware.ch>:
On 2017-11-09 19:04, Nicolas Cellier wrote:
2017-11-09 18:02 GMT+01:00 Raffaello Giulietti < raffaello.giulietti@lifeware.ch <mailto:raffaello.giulietti@lifeware.ch
:
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.
Simple contexts, like the one which is the subject of this trail, are the one we should strive at because they are the ones most likely used in day-to-day working. Having useful properties and regularity for simple cases might perhaps cover 99% of the everyday usages (just a dishonestly biased estimate ;-) )
Complex contexts, with heavy arithmetic, are best dealt by numericists when Floats are involved, or with unlimited precision numbers like Fractions by other programmers.
This differs from my experience. Float strikes in the most simple place were we put false expectation because of a different mental representation
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.
Indeed, I'm not surprised. But then 0.1 - (1/10) shall not evaluate to 0. If it evaluates to 0, then the numbers shall compare as being equal.
The surprise lies in the inconsistency between the comparison and the subtraction, not in the isolated operations.
I agree that following assertion hold: self assert: a ~= b & a isFloat & b isFloat & a isFinite & b isFinite ==> (a - b) isZero not
The arrow ==> is bidirectional even for finite Floats:
self assert: (a - b) isZero not & a isFloat & b isFloat & a isFinite & b isFinite ==> a ~= b
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
See above my observation about what I consider surprising.
As already said, it's a false expectation in the context of mixed
arithmetic.
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?
As an alternative, the Float>>asFraction method could return the Fraction with the smallest denominator that would convert to the receiver by the Fraction>>asFloat method.
So, 0.1 asFraction would return 1/10 rather than the beefy Fraction it currently returns. To return the beast, one would have to intentionally invoke asExactFraction or something similar.
This might cause less surprising behavior. But I have to think more.
No the goal here was to have a non null difference because we need to
preserve inequality for other features.
Answering anything but a Float at a high computation price goes against primary purpose of Float (speed, efficiency) If that's what we want, then we shall not use Float in the first place. That's why I don't believe in such proposal
The minimal Fraction algorithm is an intersting challenge though. Not sure how to find it... Coming back to a bit of code, we have only minimal decimal (with only powers of 2 & 5 at denominator):
{[Float pi asFraction]. [Float pi asMinimalDecimalFraction]} collect: #bench.
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.
A clearly stated rule, consistently applied and known to everybody, helps.
In presence of heterogeneous numbers, the rule should state the common denominator, so to say. Hence, the numbers involved in mixed-mode arithmetic are either all converted to one representation or all to the other: whether they are compared or added, subtracted or divided, etc. One rule for mixed-mode conversions, not two.
Having an economy of rules is allways a good idea.
If you can obtain a consistent system with 1 single rule rather than 2 then go. But if it's at the price of sacrificing higher expectations, that's another matter.
Languages that have a simpler arithmetic model, bounded integer, no Fraction, may stick to a single rule. More sofisticated models like you'll find in Lisp and Scheme have exact same logic as Squeak/Pharo.
sophisticated... (i'm on my way copying/pasting that one a thousand times)
We don't have 2 rules gratuitously as already explained.
- Total relation order of non nan values so as to be a good Magnitude citizen imply non equality - Producing Float in case of mixed arithmetic is for practicle purpose: speed (What are those damn Float for otherwise?) it's also justified a posteriori by (exact op: inexact) -> inexact
What are you ready to sacrifice/trade?
tell me why = is not a relation of equivalence anymore (not associative)
Ensuring that equality is an equivalence is always a problem when the entities involved are of different nature, like here. This is not a new problem and not inherent in numbers. (Logicians and set theorists would have much to tell.) Even comparing Points and ColoredPoints is problematic, so I have no final answer.
In Smalltalk, furthermore, implementing equality makes it necessary to (publicly) expose much more internal details about an object than in other environments.
Let's focus on Number.
Loosing equivalence is loosing ability to mix Numbers in Set. But not only Numbers... Anything having a Number somewhere in an inst var, like (1/10)@0 and 0.1@0.
On 2017-11-09 22:11, Nicolas Cellier wrote:
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?
As an alternative, the Float>>asFraction method could return the Fraction with the smallest denominator that would convert to the receiver by the Fraction>>asFloat method.
So, 0.1 asFraction would return 1/10 rather than the beefy Fraction it currently returns. To return the beast, one would have to intentionally invoke asExactFraction or something similar.
This might cause less surprising behavior. But I have to think more.
No the goal here was to have a non null difference because we need to preserve inequality for other features.
Answering anything but a Float at a high computation price goes against primary purpose of Float (speed, efficiency) If that's what we want, then we shall not use Float in the first place. That's why I don't believe in such proposal
The minimal Fraction algorithm is an intersting challenge though. Not sure how to find it...
I'm thinking of a continuous fraction expansion of the exact fraction until the partial fraction falls inside the rounding interval of the Float. Heavy, but doable. Not sure, however, if it always meets the stated criterion.
2017-11-09 23:50 GMT+01:00 <raffaello.giulietti@lifeware.ch>:
On 2017-11-09 22:11, Nicolas Cellier wrote:
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?
As an alternative, the Float>>asFraction method could return the Fraction with the smallest denominator that would convert to the receiver by the Fraction>>asFloat method.
So, 0.1 asFraction would return 1/10 rather than the beefy Fraction it currently returns. To return the beast, one would have to intentionally invoke asExactFraction or something
similar.
This might cause less surprising behavior. But I have to think
more.
No the goal here was to have a non null difference because we need to preserve inequality for other features.
Answering anything but a Float at a high computation price goes against primary purpose of Float (speed, efficiency) If that's what we want, then we shall not use Float in the first
place.
That's why I don't believe in such proposal
The minimal Fraction algorithm is an intersting challenge though. Not sure how to find it...
I'm thinking of a continuous fraction expansion of the exact fraction until the partial fraction falls inside the rounding interval of the Float.
Heavy, but doable.
Not sure, however, if it always meets the stated criterion.
then look at asApproximateFraction and change the termination condition
On 2017-11-09 22:11, Nicolas Cellier wrote:
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.
A clearly stated rule, consistently applied and known to everybody, helps.
In presence of heterogeneous numbers, the rule should state the common denominator, so to say. Hence, the numbers involved in mixed-mode arithmetic are either all converted to one representation or all to the other: whether they are compared or added, subtracted or divided, etc. One rule for mixed-mode conversions, not two.
Having an economy of rules is allways a good idea. If you can obtain a consistent system with 1 single rule rather than 2 then go. But if it's at the price of sacrificing higher expectations, that's another matter.
Languages that have a simpler arithmetic model, bounded integer, no Fraction, may stick to a single rule. More sofisticated models like you'll find in Lisp and Scheme have exact same logic as Squeak/Pharo.
sophisticated... (i'm on my way copying/pasting that one a thousand times)
We don't have 2 rules gratuitously as already explained. - Total relation order of non nan values so as to be a good Magnitude citizen imply non equality - Producing Float in case of mixed arithmetic is for practicle purpose: speed  (What are those damn Float for otherwise?)  it's also justified a posteriori by (exact op: inexact) -> inexact
What are you ready to sacrifice/trade?
Let me check if I correctly understand the reason for the dual rule regime for mixed computations: (1) preservation of = as an equivalence and of total ordering. This is ensured by converting Floats to Fractions. (2) performance in case of the 4 basic operations, which is the reason for the second conversion rule from Fractions to Floats. Now, the gain in speed by exercising (2) really depends on how the numbers "mix" in a long chain of operations. I guess for most uses of mixed arithmetic it doesn't make any noticeable difference with respect to a pure Fraction computation. Besides, correctly converting a Fraction to a Float requires more computation than the opposite. So, to answer your question, if preservation of total order and = is worthwhile even in case of mixed numbers, I would sacrifice speed for the sake of the principle of least surprise. One rule, Float->Fraction, slightly less speed. But for those cases where the gain in speed from using Floats would make a noticeable difference, we are entering the hard, counter-intuitive realm of limited precision arithmetic anyway. We better be experts in the first place. And as experts we will find a way out of the one-rule regime by performing explicit Fraction->Float conversions where needed and won't face surprises. The only reason to prefer the opposite one-rule, that would always convert Fractions to Floats in mixed computations, is compatibility with the commercial Smalltalk implementations. Granted, it's not a sound reason but a pragmatic one.
I would like to summarize my perspective of what emerged from the discussions in the "float & fraction equality bug" trail. The topic is all about mixed operations when both Fractions and Floats are involved in the mix and can be restated as the question of whether it is better to automagically convert the Float to a Fraction or the Fraction to a Float before performing the operation. AFAIK, Pharo currently implements a dual-conversion strategy: (1) it applies Float->Fraction for comparison like =, <=, etc. (2) it applies Fraction->Float for operations like +, *, etc. The reason for (1) is preservation of = as an equivalence and of <= as a total ordering. This is an important point for most Smalltalkers. The reason for (2), however, is dictated by a supposedly better performance. While it is true that Floats perform better than Fractions, I'm not sure that it makes a noticeable difference in everyday uses. Further, the Fraction->Float conversion might even cost more than the gain of using Floats for the real work, the operation itself. The conversion Float->Fraction, on the contrary, is easier. But the major disadvantage of (2) is that it enters the world of limited precision computation (e.g., Floats), which is much harder to understand, less intuitive, more surprising for most of us. So, it might be worthwhile to suppress (2) and consistently apply Float->Fraction conversions whenever needed. It won't make daily computations noticeably slower and helps in preserving more enjoyable properties than the current dual-conversion regime. Also, it won't prevent the numericists or other practitioners to do floating point computations in mixed contexts: just apply explicit Fraction->Float conversions when so desired. This will be at odd with other Smalltalk implementations but might end up being a safer environment. I would like to thank Nicolas in particular for being so quick in answering back and for the good points he raised. Greetings Raffaello
Good summary. I must add to comparison and operations, Iâd add encoding, where ASN.1 Reals MUST convert to Float before encoding with mantissa, exponent, etc. Here is another Fraction -> Float conversion. As well as ScaledDecimal -> Float conversion. Sent from ProtonMail Mobile On Fri, Nov 10, 2017 at 06:59, <raffaello.giulietti@lifeware.ch> wrote:
I would like to summarize my perspective of what emerged from the discussions in the "float & fraction equality bug" trail. The topic is all about mixed operations when both Fractions and Floats are involved in the mix and can be restated as the question of whether it is better to automagically convert the Float to a Fraction or the Fraction to a Float before performing the operation. AFAIK, Pharo currently implements a dual-conversion strategy: (1) it applies Float->Fraction for comparison like =, <=, etc. (2) it applies Fraction->Float for operations like +, *, etc. The reason for (1) is preservation of = as an equivalence and of <= as a total ordering. This is an important point for most Smalltalkers. The reason for (2), however, is dictated by a supposedly better performance. While it is true that Floats perform better than Fractions, I'm not sure that it makes a noticeable difference in everyday uses. Further, the Fraction->Float conversion might even cost more than the gain of using Floats for the real work, the operation itself. The conversion Float->Fraction, on the contrary, is easier. But the major disadvantage of (2) is that it enters the world of limited precision computation (e.g., Floats), which is much harder to understand, less intuitive, more surprising for most of us. So, it might be worthwhile to suppress (2) and consistently apply Float->Fraction conversions whenever needed. It won't make daily computations noticeably slower and helps in preserving more enjoyable properties than the current dual-conversion regime. Also, it won't prevent the numericists or other practitioners to do floating point computations in mixed contexts: just apply explicit Fraction->Float conversions when so desired. This will be at odd with other Smalltalk implementations but might end up being a safer environment. I would like to thank Nicolas in particular for being so quick in answering back and for the good points he raised. Greetings Raffaello
Thanks indeed for the summary. I like this. Doru
On Nov 10, 2017, at 12:59 PM, raffaello.giulietti@lifeware.ch wrote:
I would like to summarize my perspective of what emerged from the discussions in the "float & fraction equality bug" trail.
The topic is all about mixed operations when both Fractions and Floats are involved in the mix and can be restated as the question of whether it is better to automagically convert the Float to a Fraction or the Fraction to a Float before performing the operation.
AFAIK, Pharo currently implements a dual-conversion strategy: (1) it applies Float->Fraction for comparison like =, <=, etc. (2) it applies Fraction->Float for operations like +, *, etc.
The reason for (1) is preservation of = as an equivalence and of <= as a total ordering. This is an important point for most Smalltalkers.
The reason for (2), however, is dictated by a supposedly better performance. While it is true that Floats perform better than Fractions, I'm not sure that it makes a noticeable difference in everyday uses. Further, the Fraction->Float conversion might even cost more than the gain of using Floats for the real work, the operation itself. The conversion Float->Fraction, on the contrary, is easier.
But the major disadvantage of (2) is that it enters the world of limited precision computation (e.g., Floats), which is much harder to understand, less intuitive, more surprising for most of us.
So, it might be worthwhile to suppress (2) and consistently apply Float->Fraction conversions whenever needed. It won't make daily computations noticeably slower and helps in preserving more enjoyable properties than the current dual-conversion regime.
Also, it won't prevent the numericists or other practitioners to do floating point computations in mixed contexts: just apply explicit Fraction->Float conversions when so desired.
This will be at odd with other Smalltalk implementations but might end up being a safer environment.
I would like to thank Nicolas in particular for being so quick in answering back and for the good points he raised.
Greetings Raffaello
-- www.tudorgirba.com www.feenk.com "Yesterday is a fact. Tomorrow is a possibility. Today is a challenge."
I think we should also mention that the literal 0.1 is not the number in base ten that we all learned in school, despite both using base 10 for printing and despite both being printed the same way - this is the crux of the problem. But there is such a number in the system, called ScaledDecimal, for which the equality stands: 0.1s = (1/10) We could have chosen ScaledDecimal as backing for our decimal literals (the literal 0.1 being translated by the parser/compiler as a ScaledDecimal and only have say the literal 0.1f being translated to an imprecise float) and we would not have had this problem. We could probably still do it Florin On 11/10/2017 7:18 AM, Tudor Girba wrote:
Thanks indeed for the summary. I like this.
Doru
On Nov 10, 2017, at 12:59 PM, raffaello.giulietti@lifeware.ch wrote:
I would like to summarize my perspective of what emerged from the discussions in the "float & fraction equality bug" trail.
The topic is all about mixed operations when both Fractions and Floats are involved in the mix and can be restated as the question of whether it is better to automagically convert the Float to a Fraction or the Fraction to a Float before performing the operation.
AFAIK, Pharo currently implements a dual-conversion strategy: (1) it applies Float->Fraction for comparison like =, <=, etc. (2) it applies Fraction->Float for operations like +, *, etc.
The reason for (1) is preservation of = as an equivalence and of <= as a total ordering. This is an important point for most Smalltalkers.
The reason for (2), however, is dictated by a supposedly better performance. While it is true that Floats perform better than Fractions, I'm not sure that it makes a noticeable difference in everyday uses. Further, the Fraction->Float conversion might even cost more than the gain of using Floats for the real work, the operation itself. The conversion Float->Fraction, on the contrary, is easier.
But the major disadvantage of (2) is that it enters the world of limited precision computation (e.g., Floats), which is much harder to understand, less intuitive, more surprising for most of us.
So, it might be worthwhile to suppress (2) and consistently apply Float->Fraction conversions whenever needed. It won't make daily computations noticeably slower and helps in preserving more enjoyable properties than the current dual-conversion regime.
Also, it won't prevent the numericists or other practitioners to do floating point computations in mixed contexts: just apply explicit Fraction->Float conversions when so desired.
This will be at odd with other Smalltalk implementations but might end up being a safer environment.
I would like to thank Nicolas in particular for being so quick in answering back and for the good points he raised.
Greetings Raffaello
-- www.tudorgirba.com www.feenk.com
"Yesterday is a fact. Tomorrow is a possibility. Today is a challenge."
On 11/10/2017 9:39 AM, Florin Mateoc wrote:
I think we should also mention that the literal 0.1 is not the number in base ten that we all learned in school, despite both using base 10 for printing and despite both being printed the same way - this is the crux of the problem.
But there is such a number in the system, called ScaledDecimal, for which the equality stands:
0.1s = (1/10)
We could have chosen ScaledDecimal as backing for our decimal literals (the literal 0.1 being translated by the parser/compiler as a ScaledDecimal and only have say the literal 0.1f being translated to an imprecise float) and we would not have had this problem. We could probably still do it
Florin
Sorry to follow up on myself, but the more I think about it, the more I like my own proposal It would make the choice/compromise to be fast but imprecise explicit
On 11/10/2017 7:18 AM, Tudor Girba wrote:
Thanks indeed for the summary. I like this.
Doru
On Nov 10, 2017, at 12:59 PM, raffaello.giulietti@lifeware.ch wrote:
I would like to summarize my perspective of what emerged from the discussions in the "float & fraction equality bug" trail.
The topic is all about mixed operations when both Fractions and Floats are involved in the mix and can be restated as the question of whether it is better to automagically convert the Float to a Fraction or the Fraction to a Float before performing the operation.
AFAIK, Pharo currently implements a dual-conversion strategy: (1) it applies Float->Fraction for comparison like =, <=, etc. (2) it applies Fraction->Float for operations like +, *, etc.
The reason for (1) is preservation of = as an equivalence and of <= as a total ordering. This is an important point for most Smalltalkers.
The reason for (2), however, is dictated by a supposedly better performance. While it is true that Floats perform better than Fractions, I'm not sure that it makes a noticeable difference in everyday uses. Further, the Fraction->Float conversion might even cost more than the gain of using Floats for the real work, the operation itself. The conversion Float->Fraction, on the contrary, is easier.
But the major disadvantage of (2) is that it enters the world of limited precision computation (e.g., Floats), which is much harder to understand, less intuitive, more surprising for most of us.
So, it might be worthwhile to suppress (2) and consistently apply Float->Fraction conversions whenever needed. It won't make daily computations noticeably slower and helps in preserving more enjoyable properties than the current dual-conversion regime.
Also, it won't prevent the numericists or other practitioners to do floating point computations in mixed contexts: just apply explicit Fraction->Float conversions when so desired.
This will be at odd with other Smalltalk implementations but might end up being a safer environment.
I would like to thank Nicolas in particular for being so quick in answering back and for the good points he raised.
Greetings Raffaello
-- www.tudorgirba.com www.feenk.com
"Yesterday is a fact. Tomorrow is a possibility. Today is a challenge."
On 11/10/2017 03:59 AM, raffaello.giulietti@lifeware.ch wrote:
I would like to summarize my perspective of what emerged from the discussions in the "float & fraction equality bug" trail.
The topic is all about mixed operations when both Fractions and Floats are involved in the mix and can be restated as the question of whether it is better to automagically convert the Float to a Fraction or the Fraction to a Float before performing the operation.
AFAIK, Pharo currently implements a dual-conversion strategy: (1) it applies Float->Fraction for comparison like =, <=, etc. (2) it applies Fraction->Float for operations like +, *, etc.
[...] Thanks for the summary, Raffaello. One can choose to convert Float -> Fraction (because fraction is the more general format, in that it can represent more of the real numbers) or to convert Fraction->Float (because a Float is considered an approximation of a real number). But more important, I think, is that whichever choice is made, the *same* choice must be made in *all* operations that involve both Floats and Fractions. Regards, -Martin
On Nov 10, 2017, at 7:45 PM, Martin McClure <martin@hand2mouse.com> wrote:
On 11/10/2017 03:59 AM, raffaello.giulietti@lifeware.ch wrote:
I would like to summarize my perspective of what emerged from the discussions in the "float & fraction equality bug" trail.
The topic is all about mixed operations when both Fractions and Floats are involved in the mix and can be restated as the question of whether it is better to automagically convert the Float to a Fraction or the Fraction to a Float before performing the operation.
AFAIK, Pharo currently implements a dual-conversion strategy: (1) it applies Float->Fraction for comparison like =, <=, etc. (2) it applies Fraction->Float for operations like +, *, etc.
[...]
Thanks for the summary, Raffaello. One can choose to convert Float -> Fraction (because fraction is the more general format, in that it can represent more of the real numbers) or to convert Fraction->Float (because a Float is considered an approximation of a real number).
But more important, I think, is that whichever choice is made, the *same* choice must be made in *all* operations that involve both Floats and Fractions.
Indeed, this was the source of my original confusion. Doru
Regards, -Martin
-- www.tudorgirba.com www.feenk.com "Presenting is storytelling."
On 2017-11-10 19:45, Martin McClure wrote:
On 11/10/2017 03:59 AM, raffaello.giulietti@lifeware.ch wrote:
I would like to summarize my perspective of what emerged from the discussions in the "float & fraction equality bug" trail.
The topic is all about mixed operations when both Fractions and Floats are involved in the mix and can be restated as the question of whether it is better to automagically convert the Float to a Fraction or the Fraction to a Float before performing the operation.
AFAIK, Pharo currently implements a dual-conversion strategy: (1) it applies Float->Fraction for comparison like =, <=, etc. (2) it applies Fraction->Float for operations like +, *, etc.
[...]
Thanks for the summary, Raffaello. One can choose to convert Float -> Fraction (because fraction is the more general format, in that it can represent more of the real numbers) or to convert Fraction->Float (because a Float is considered an approximation of a real number).
But more important, I think, is that whichever choice is made, the *same* choice must be made in *all* operations that involve both Floats and Fractions.
Regards, -Martin
Doing only Fraction->Float conversions in mixed mode won't preserve = as an equivalence relation and won't enable a consistent ordering with <=, which probably most Smalltalkers consider important and enjoyable properties. Nicolas gave some convincing examples on why most programmers might want to rely on them. Also, as I mentioned, most Smalltalkers might prefer keeping away from the complex properties of Floats. Doing automatic, implicit Fraction->Float conversions behind the scenes only exacerbates the probability of encountering Floats and of having to deal with their weird and unfamiliar arithmetic.
On 11/10/2017 11:33 AM, raffaello.giulietti@lifeware.ch wrote:
Doing only Fraction->Float conversions in mixed mode won't preserve = as an equivalence relation and won't enable a consistent ordering with <=, which probably most Smalltalkers consider important and enjoyable properties. Good point. I agree that Float -> Fraction is the more desirable mode for implicit conversion, since it can always be done without changing the value. Nicolas gave some convincing examples on why most programmers might want to rely on them.
Also, as I mentioned, most Smalltalkers might prefer keeping away from the complex properties of Floats. Doing automatic, implicit Fraction->Float conversions behind the scenes only exacerbates the probability of encountering Floats and of having to deal with their weird and unfamiliar arithmetic. One problem is that we make it easy to create Floats in source code (0.1), and we print Floats in a nice decimal format but by default print Fractions in their reduced fractional form. If we didn't do this, Smalltalkers might not be working with Floats in the first place, and if they did not have any Floats in their computation they would never run into an implicit conversion to *or* from Float.
As it is, if we were to uniformly do Float -> Fraction conversion on mixed-mode operations, we would get things like (0.1 * (1/1)) printString                      --> '3602879701896397/36028797018963968' Not incredibly friendly. Regards, -Martin
2017-11-10 20:58 GMT+01:00 Martin McClure <martin@hand2mouse.com>:
On 11/10/2017 11:33 AM, raffaello.giulietti@lifeware.ch wrote:
Doing only Fraction->Float conversions in mixed mode won't preserve = as an equivalence relation and won't enable a consistent ordering with <=, which probably most Smalltalkers consider important and enjoyable properties.
Good point. I agree that Float -> Fraction is the more desirable mode for implicit conversion, since it can always be done without changing the value.
Nicolas gave some convincing examples on why most programmers might want to rely on them.
Also, as I mentioned, most Smalltalkers might prefer keeping away from the complex properties of Floats. Doing automatic, implicit Fraction->Float conversions behind the scenes only exacerbates the probability of encountering Floats and of having to deal with their weird and unfamiliar arithmetic.
One problem is that we make it easy to create Floats in source code (0.1), and we print Floats in a nice decimal format but by default print Fractions in their reduced fractional form. If we didn't do this, Smalltalkers might not be working with Floats in the first place, and if they did not have any Floats in their computation they would never run into an implicit conversion to *or* from Float.
As it is, if we were to uniformly do Float -> Fraction conversion on mixed-mode operations, we would get things like
(0.1 * (1/1)) printString --> '3602879701896397/36028797018963968'
Not incredibly friendly.
For those not practicing the litote: definitely a no go.
Regards, -Martin
At the risk of repeating myself, unique choice for all operations is a nice to have but not a goal per se. I mostly agree with Florin: having 0.1 representing a decimal rather than a Float might be a better path meeting more expectations. But thinking that it will magically eradicate the problems is a myth. Shall precision be limited or shall we use ScaledDecimals ? With limited precision, we'll be back to having several Fraction converting to same LimitedDecimal, so it won't solve anything wrt original problem. With illimted precision we'll have the bad property that what we print does not re-interpret to the same ScaledDecimal (0.1s / 0.3s), but that's a detail. The worse thing is that long chain of operations will tend to produce monster numerators denominators. And we will all have long chain when resizing a morph with proportional layout in scalable graphics. We will then have to insert rounding operations manually for mitigating the problem, and somehow reinvent a more inconvenient Float... It's boring to allways play the role of Cassandra, but why do you think that Scheme and Lisp did not choose that path?
On 11/10/2017 4:18 PM, Nicolas Cellier wrote:
2017-11-10 20:58 GMT+01:00 Martin McClure <martin@hand2mouse.com <mailto:martin@hand2mouse.com>>:
On 11/10/2017 11:33 AM, raffaello.giulietti@lifeware.ch <mailto:raffaello.giulietti@lifeware.ch> wrote:
Doing only Fraction->Float conversions in mixed mode won't preserve = as an equivalence relation and won't enable a consistent ordering with <=, which probably most Smalltalkers consider important and enjoyable properties.
Good point. I agree that Float -> Fraction is the more desirable mode for implicit conversion, since it can always be done without changing the value.
Nicolas gave some convincing examples on why most programmers might want to rely on them.
Also, as I mentioned, most Smalltalkers might prefer keeping away from the complex properties of Floats. Doing automatic, implicit Fraction->Float conversions behind the scenes only exacerbates the probability of encountering Floats and of having to deal with their weird and unfamiliar arithmetic.
One problem is that we make it easy to create Floats in source code (0.1), and we print Floats in a nice decimal format but by default print Fractions in their reduced fractional form. If we didn't do this, Smalltalkers might not be working with Floats in the first place, and if they did not have any Floats in their computation they would never run into an implicit conversion to *or* from Float.
As it is, if we were to uniformly do Float -> Fraction conversion on mixed-mode operations, we would get things like
(0.1 * (1/1)) printString                      --> '3602879701896397/36028797018963968'
Not incredibly friendly.
For those not practicing the litote: definitely a no go.
Regards, -Martin
At the risk of repeating myself, unique choice for all operations is a nice to have but not a goal per se.
I mostly agree with Florin: having 0.1 representing a decimal rather than a Float might be a better path meeting more expectations.
But thinking that it will magically eradicate the problems is a myth.
Shall precision be limited or shall we use ScaledDecimals ?
With limited precision, we'll be back to having several Fraction converting to same LimitedDecimal, so it won't solve anything wrt original problem.
I don't think people have problems with understanding limited precision. We do know that when we write 3.14.. we did not write Pi. As long as it does not contradict the intuition that we developed early on (and that's the problem with the binary representation of floats, not that it is limited)...
With illimted precision we'll have the bad property that what we print does not re-interpret to the same ScaledDecimal (0.1s / 0.3s), but that's a detail. The worse thing is that long chain of operations will tend to produce monster numerators denominators. And we will all have long chain when resizing a morph with proportional layout in scalable graphics.
But I did not propose to drop floats, just to change the visible representation of literals: 0.1 would mean a ScaledDecimal literal and 0.1f would mean a float literal. The extra "f" would be a constant reminder that something special is going on (the dangers of interpreting the literal as an exact representation in base 10).
We will then have to insert rounding operations manually for mitigating the problem, and somehow reinvent a more inconvenient Float...
I don't think we would have to reinvent Float - this one would stay exactly the same (other than printing the extra f (or d)). But I agree that you raise an important point with precision/scale. We would indeed need to make ScaledDecimal more complicated. Because today 0.1s * 0.1s is printed as 0.0s1, even though 0.1s * 0.1s = (1/100) evaluates to true, which is nice.
It's boring to allways play the role of Cassandra, but why do you think that Scheme and Lisp did not choose that path?
Oh. come on, if Scheme and Lisp did it all already, why are we all here? :)
On 11/10/2017 4:42 PM, Florin Mateoc wrote:
On 11/10/2017 4:18 PM, Nicolas Cellier wrote:
2017-11-10 20:58 GMT+01:00 Martin McClure <martin@hand2mouse.com <mailto:martin@hand2mouse.com>>:
On 11/10/2017 11:33 AM, raffaello.giulietti@lifeware.ch <mailto:raffaello.giulietti@lifeware.ch> wrote:
Doing only Fraction->Float conversions in mixed mode won't preserve = as an equivalence relation and won't enable a consistent ordering with <=, which probably most Smalltalkers consider important and enjoyable properties.
Good point. I agree that Float -> Fraction is the more desirable mode for implicit conversion, since it can always be done without changing the value.
Nicolas gave some convincing examples on why most programmers might want to rely on them.
Also, as I mentioned, most Smalltalkers might prefer keeping away from the complex properties of Floats. Doing automatic, implicit Fraction->Float conversions behind the scenes only exacerbates the probability of encountering Floats and of having to deal with their weird and unfamiliar arithmetic.
One problem is that we make it easy to create Floats in source code (0.1), and we print Floats in a nice decimal format but by default print Fractions in their reduced fractional form. If we didn't do this, Smalltalkers might not be working with Floats in the first place, and if they did not have any Floats in their computation they would never run into an implicit conversion to *or* from Float.
As it is, if we were to uniformly do Float -> Fraction conversion on mixed-mode operations, we would get things like
(0.1 * (1/1)) printString                      --> '3602879701896397/36028797018963968'
Not incredibly friendly.
For those not practicing the litote: definitely a no go.
Regards, -Martin
At the risk of repeating myself, unique choice for all operations is a nice to have but not a goal per se.
I mostly agree with Florin: having 0.1 representing a decimal rather than a Float might be a better path meeting more expectations.
But thinking that it will magically eradicate the problems is a myth.
Shall precision be limited or shall we use ScaledDecimals ?
With limited precision, we'll be back to having several Fraction converting to same LimitedDecimal, so it won't solve anything wrt original problem.
I don't think people have problems with understanding limited precision. We do know that when we write 3.14.. we did not write Pi. As long as it does not contradict the intuition that we developed early on (and that's the problem with the binary representation of floats, not that it is limited)...
With illimted precision we'll have the bad property that what we print does not re-interpret to the same ScaledDecimal (0.1s / 0.3s), but that's a detail. The worse thing is that long chain of operations will tend to produce monster numerators denominators. And we will all have long chain when resizing a morph with proportional layout in scalable graphics.
But I did not propose to drop floats, just to change the visible representation of literals: 0.1 would mean a ScaledDecimal literal and 0.1f would mean a float literal. The extra "f" would be a constant reminder that something special is going on (the dangers of interpreting the literal as an exact representation in base 10).
And there would still be a lot of places where we would continue to judiciously use floats, such as in morphic. Just like today people don't use fractions when they don't need their extra precision - ah, who am I kidding? There are a lot of developers out there who would go to great lengths to save an extra keystroke. :) Well, I think it could be made to work (e.g. one of the low-hanging fixes would be that multiplication of scaledDecimals would add their scales) and it would complement nicely Smalltalk's seamless and intuitive largeIntegers - just like largeIntegers liberate us from the tyranny of the hardware representation for integers, we should also not be prisoners to the hardware representation of floats.
We will then have to insert rounding operations manually for mitigating the problem, and somehow reinvent a more inconvenient Float...
I don't think we would have to reinvent Float - this one would stay exactly the same (other than printing the extra f (or d)). But I agree that you raise an important point with precision/scale. We would indeed need to make ScaledDecimal more complicated. Because today 0.1s * 0.1s is printed as 0.0s1, even though 0.1s * 0.1s = (1/100) evaluates to true, which is nice.
It's boring to allways play the role of Cassandra, but why do you think that Scheme and Lisp did not choose that path?
Oh. come on, if Scheme and Lisp did it all already, why are we all here? :)
2017-11-10 23:04 GMT+01:00 Florin Mateoc <fmateoc@gmail.com>:
On 11/10/2017 4:42 PM, Florin Mateoc wrote:
On 11/10/2017 4:18 PM, Nicolas Cellier wrote:
2017-11-10 20:58 GMT+01:00 Martin McClure <martin@hand2mouse.com>:
On 11/10/2017 11:33 AM, raffaello.giulietti@lifeware.ch wrote:
Doing only Fraction->Float conversions in mixed mode won't preserve = as an equivalence relation and won't enable a consistent ordering with <=, which probably most Smalltalkers consider important and enjoyable properties.
Good point. I agree that Float -> Fraction is the more desirable mode for implicit conversion, since it can always be done without changing the value.
Nicolas gave some convincing examples on why most programmers might want to rely on them.
Also, as I mentioned, most Smalltalkers might prefer keeping away from the complex properties of Floats. Doing automatic, implicit Fraction->Float conversions behind the scenes only exacerbates the probability of encountering Floats and of having to deal with their weird and unfamiliar arithmetic.
One problem is that we make it easy to create Floats in source code (0.1), and we print Floats in a nice decimal format but by default print Fractions in their reduced fractional form. If we didn't do this, Smalltalkers might not be working with Floats in the first place, and if they did not have any Floats in their computation they would never run into an implicit conversion to *or* from Float.
As it is, if we were to uniformly do Float -> Fraction conversion on mixed-mode operations, we would get things like
(0.1 * (1/1)) printString --> '3602879701896397/36028797018963968'
Not incredibly friendly.
For those not practicing the litote: definitely a no go.
Regards, -Martin
At the risk of repeating myself, unique choice for all operations is a nice to have but not a goal per se.
I mostly agree with Florin: having 0.1 representing a decimal rather than a Float might be a better path meeting more expectations.
But thinking that it will magically eradicate the problems is a myth.
Shall precision be limited or shall we use ScaledDecimals ?
With limited precision, we'll be back to having several Fraction converting to same LimitedDecimal, so it won't solve anything wrt original problem.
I don't think people have problems with understanding limited precision. We do know that when we write 3.14.. we did not write Pi. As long as it does not contradict the intuition that we developed early on (and that's the problem with the binary representation of floats, not that it is limited)...
The problem is about wanting to preserve equivalence. With limited precision, forget about (a - b) isZero = (a=b), whatever the base we're using for printing numbers.
With illimted precision we'll have the bad property that what we print does not re-interpret to the same ScaledDecimal (0.1s / 0.3s), but that's a detail. The worse thing is that long chain of operations will tend to produce monster numerators denominators. And we will all have long chain when resizing a morph with proportional layout in scalable graphics.
But I did not propose to drop floats, just to change the visible representation of literals: 0.1 would mean a ScaledDecimal literal and 0.1f would mean a float literal. The extra "f" would be a constant reminder that something special is going on (the dangers of interpreting the literal as an exact representation in base 10).
And there would still be a lot of places where we would continue to judiciously use floats, such as in morphic. Just like today people don't use fractions when they don't need their extra precision - ah, who am I kidding?
Of course, most of the places where we use Float today in fact. Doesn't it mean that ScaledDecimals would solve essentially the problems we don't have? The major outcome is if we can have some notation for float telling about the rounding. Currently with syntactic color we can already mark Float that are not exactly the decimal they pretend to be without changing the syntax at all. So let's do that immediately. There are a lot of developers out there who would go to great lengths to
save an extra keystroke. :) Well, I think it could be made to work (e.g. one of the low-hanging fixes would be that multiplication of scaledDecimals would add their scales) and it would complement nicely Smalltalk's seamless and intuitive largeIntegers - just like largeIntegers liberate us from the tyranny of the hardware representation for integers, we should also not be prisoners to the hardware representation of floats.
But then division is the little grain of sand in the cog which makes ScaledDecimals to not scale. All fraction have a finite number of digits which are repeated, so it's not completely unsolvable. We could write 1.2357575757... as 1.23(57) for example, meaning 57 repeated ad libitum. In order to preserve nice properties of REPL we would completely throw the scale away and just have Decimals. The scale would be what it should: just a parameter passed to a variety of printing. We will then have to insert rounding operations manually for mitigating the problem, and somehow reinvent a more inconvenient Float... I don't think we would have to reinvent Float - this one would stay exactly the same (other than printing the extra f (or d)). But I agree that you raise an important point with precision/scale. We would indeed need to make ScaledDecimal more complicated. Because today 0.1s * 0.1s is printed as 0.0s1, even though 0.1s * 0.1s = (1/100) evaluates to true, which is nice. It's boring to allways play the role of Cassandra, but why do you think that Scheme and Lisp did not choose that path? Oh. come on, if Scheme and Lisp did it all already, why are we all here? :)
2017-11-10 22:42 GMT+01:00 Florin Mateoc <fmateoc@gmail.com>:
On 11/10/2017 4:18 PM, Nicolas Cellier wrote:
2017-11-10 20:58 GMT+01:00 Martin McClure <martin@hand2mouse.com>:
On 11/10/2017 11:33 AM, raffaello.giulietti@lifeware.ch wrote:
Doing only Fraction->Float conversions in mixed mode won't preserve = as an equivalence relation and won't enable a consistent ordering with <=, which probably most Smalltalkers consider important and enjoyable properties.
Good point. I agree that Float -> Fraction is the more desirable mode for implicit conversion, since it can always be done without changing the value.
Nicolas gave some convincing examples on why most programmers might want to rely on them.
Also, as I mentioned, most Smalltalkers might prefer keeping away from the complex properties of Floats. Doing automatic, implicit Fraction->Float conversions behind the scenes only exacerbates the probability of encountering Floats and of having to deal with their weird and unfamiliar arithmetic.
One problem is that we make it easy to create Floats in source code (0.1), and we print Floats in a nice decimal format but by default print Fractions in their reduced fractional form. If we didn't do this, Smalltalkers might not be working with Floats in the first place, and if they did not have any Floats in their computation they would never run into an implicit conversion to *or* from Float.
As it is, if we were to uniformly do Float -> Fraction conversion on mixed-mode operations, we would get things like
(0.1 * (1/1)) printString --> '3602879701896397/36028797018963968'
Not incredibly friendly.
For those not practicing the litote: definitely a no go.
Regards, -Martin
At the risk of repeating myself, unique choice for all operations is a nice to have but not a goal per se.
I mostly agree with Florin: having 0.1 representing a decimal rather than a Float might be a better path meeting more expectations.
But thinking that it will magically eradicate the problems is a myth.
Shall precision be limited or shall we use ScaledDecimals ?
With limited precision, we'll be back to having several Fraction converting to same LimitedDecimal, so it won't solve anything wrt original problem.
I don't think people have problems with understanding limited precision. We do know that when we write 3.14.. we did not write Pi. As long as it does not contradict the intuition that we developed early on (and that's the problem with the binary representation of floats, not that it is limited)...
With illimted precision we'll have the bad property that what we print does not re-interpret to the same ScaledDecimal (0.1s / 0.3s), but that's a detail. The worse thing is that long chain of operations will tend to produce monster numerators denominators. And we will all have long chain when resizing a morph with proportional layout in scalable graphics.
But I did not propose to drop floats, just to change the visible representation of literals: 0.1 would mean a ScaledDecimal literal and 0.1f would mean a float literal. The extra "f" would be a constant reminder that something special is going on (the dangers of interpreting the literal as an exact representation in base 10).
We will then have to insert rounding operations manually for mitigating the problem, and somehow reinvent a more inconvenient Float...
I don't think we would have to reinvent Float - this one would stay exactly the same (other than printing the extra f (or d)). But I agree that you raise an important point with precision/scale. We would indeed need to make ScaledDecimal more complicated. Because today 0.1s * 0.1s is printed as 0.0s1, even though 0.1s * 0.1s = (1/100) evaluates to true, which is nice.
It's boring to allways play the role of Cassandra, but why do you think that Scheme and Lisp did not choose that path?
Oh. come on, if Scheme and Lisp did it all already, why are we all here? :)
Not at all, here it what it means: We must analyze why some decisions were taken, and analyze what condition changed to enable different solutions. Maths did not change so much, so it must be something else... Fast CPU and huge memory could be a game changer, but how do the LargeInteger operations scale?
On 2017-11-10 22:18, Nicolas Cellier wrote:
2017-11-10 20:58 GMT+01:00 Martin McClure <martin@hand2mouse.com <mailto:martin@hand2mouse.com>>:
On 11/10/2017 11:33 AM, raffaello.giulietti@lifeware.ch <mailto:raffaello.giulietti@lifeware.ch> wrote:
Doing only Fraction->Float conversions in mixed mode won't preserve = as an equivalence relation and won't enable a consistent ordering with <=, which probably most Smalltalkers consider important and enjoyable properties.
Good point. I agree that Float -> Fraction is the more desirable mode for implicit conversion, since it can always be done without changing the value.
Nicolas gave some convincing examples on why most programmers might want to rely on them.
Also, as I mentioned, most Smalltalkers might prefer keeping away from the complex properties of Floats. Doing automatic, implicit Fraction->Float conversions behind the scenes only exacerbates the probability of encountering Floats and of having to deal with their weird and unfamiliar arithmetic.
One problem is that we make it easy to create Floats in source code (0.1), and we print Floats in a nice decimal format but by default print Fractions in their reduced fractional form. If we didn't do this, Smalltalkers might not be working with Floats in the first place, and if they did not have any Floats in their computation they would never run into an implicit conversion to *or* from Float.
As it is, if we were to uniformly do Float -> Fraction conversion on mixed-mode operations, we would get things like
(0.1 * (1/1)) printString                      --> '3602879701896397/36028797018963968'
Not incredibly friendly.
For those not practicing the litote: definitely a no go.
Regards, -Martin
At the risk of repeating myself, unique choice for all operations is a nice to have but not a goal per se.
I mostly agree with Florin: having 0.1 representing a decimal rather than a Float might be a better path meeting more expectations.
But thinking that it will magically eradicate the problems is a myth.
Shall precision be limited or shall we use ScaledDecimals ?
With limited precision, we'll be back to having several Fraction converting to same LimitedDecimal, so it won't solve anything wrt original problem. With illimted precision we'll have the bad property that what we print does not re-interpret to the same ScaledDecimal (0.1s / 0.3s), but that's a detail. The worse thing is that long chain of operations will tend to produce monster numerators denominators. And we will all have long chain when resizing a morph with proportional layout in scalable graphics. We will then have to insert rounding operations manually for mitigating the problem, and somehow reinvent a more inconvenient Float... It's boring to allways play the role of Cassandra, but why do you think that Scheme and Lisp did not choose that path?
I don't know why Lisper got this way. They justify the conversion choice for comparison (equivalence of = and total ordering of <=) but for the arithmetic operations there does not seem to be a clearly stated rationale. Are they happy with their choice? I haven't the foggiest idea. Don't get me wrong: I understand the Fraction->Float choice for the sake of more speed. But this increase in speed is not functionally transparent: it is not like an increase in the clock rate of a CPU, it's not like a better performing division algorithm, a faster implementation of sorting or a JIT compiler. This increase in speed, rather, comes at the cost of a shift in functionality and cognitive load, because even + or 0.1 have not their conventional semantics. It's not that Floats are wrong by themselves, it's that their operations seem weird at first and at odd with the familiar behavior. Again, I'm not addressing the experts in floating point computation here: they know how to deal with it. Thus, I expect that the developers of numerically intensive libraries or packages, like 2D or 3D graphics, are fully aware of the trade-offs and are in a position to make an explicit choice: either monster denominators in fractions or more care with floats, but always in control. Here, I'm targeting the John Doe Smalltalker which usually uses well-crafted, well-performing libraries and probably only performs a few thousand mixed Fraction/Float computations on his own, and then speed trade-offs do not matter. He is better served with implicit, automatic Float->Fraction conversions in both comparisons and operations. For him, less cognitive load and less surprises are probably an added value.
Hi, Indeed, I agree with this point of view. Can we distill a concrete path to action from this? Cheers, Doru
On Nov 11, 2017, at 11:58 AM, raffaello.giulietti@lifeware.ch wrote:
On 2017-11-10 22:18, Nicolas Cellier wrote:
2017-11-10 20:58 GMT+01:00 Martin McClure <martin@hand2mouse.com <mailto:martin@hand2mouse.com>>:
On 11/10/2017 11:33 AM, raffaello.giulietti@lifeware.ch <mailto:raffaello.giulietti@lifeware.ch> wrote:
Doing only Fraction->Float conversions in mixed mode won't preserve = as an equivalence relation and won't enable a consistent ordering with <=, which probably most Smalltalkers consider important and enjoyable properties.
Good point. I agree that Float -> Fraction is the more desirable mode for implicit conversion, since it can always be done without changing the value.
Nicolas gave some convincing examples on why most programmers might want to rely on them.
Also, as I mentioned, most Smalltalkers might prefer keeping away from the complex properties of Floats. Doing automatic, implicit Fraction->Float conversions behind the scenes only exacerbates the probability of encountering Floats and of having to deal with their weird and unfamiliar arithmetic.
One problem is that we make it easy to create Floats in source code (0.1), and we print Floats in a nice decimal format but by default print Fractions in their reduced fractional form. If we didn't do this, Smalltalkers might not be working with Floats in the first place, and if they did not have any Floats in their computation they would never run into an implicit conversion to *or* from Float.
As it is, if we were to uniformly do Float -> Fraction conversion on mixed-mode operations, we would get things like
(0.1 * (1/1)) printString --> '3602879701896397/36028797018963968'
Not incredibly friendly.
For those not practicing the litote: definitely a no go.
Regards, -Martin
At the risk of repeating myself, unique choice for all operations is a nice to have but not a goal per se.
I mostly agree with Florin: having 0.1 representing a decimal rather than a Float might be a better path meeting more expectations.
But thinking that it will magically eradicate the problems is a myth.
Shall precision be limited or shall we use ScaledDecimals ?
With limited precision, we'll be back to having several Fraction converting to same LimitedDecimal, so it won't solve anything wrt original problem. With illimted precision we'll have the bad property that what we print does not re-interpret to the same ScaledDecimal (0.1s / 0.3s), but that's a detail. The worse thing is that long chain of operations will tend to produce monster numerators denominators. And we will all have long chain when resizing a morph with proportional layout in scalable graphics. We will then have to insert rounding operations manually for mitigating the problem, and somehow reinvent a more inconvenient Float... It's boring to allways play the role of Cassandra, but why do you think that Scheme and Lisp did not choose that path?
I don't know why Lisper got this way. They justify the conversion choice for comparison (equivalence of = and total ordering of <=) but for the arithmetic operations there does not seem to be a clearly stated rationale.
Are they happy with their choice? I haven't the foggiest idea.
Don't get me wrong: I understand the Fraction->Float choice for the sake of more speed.
But this increase in speed is not functionally transparent: it is not like an increase in the clock rate of a CPU, it's not like a better performing division algorithm, a faster implementation of sorting or a JIT compiler.
This increase in speed, rather, comes at the cost of a shift in functionality and cognitive load, because even + or 0.1 have not their conventional semantics. It's not that Floats are wrong by themselves, it's that their operations seem weird at first and at odd with the familiar behavior.
Again, I'm not addressing the experts in floating point computation here: they know how to deal with it. Thus, I expect that the developers of numerically intensive libraries or packages, like 2D or 3D graphics, are fully aware of the trade-offs and are in a position to make an explicit choice: either monster denominators in fractions or more care with floats, but always in control.
Here, I'm targeting the John Doe Smalltalker which usually uses well-crafted, well-performing libraries and probably only performs a few thousand mixed Fraction/Float computations on his own, and then speed trade-offs do not matter. He is better served with implicit, automatic Float->Fraction conversions in both comparisons and operations.
For him, less cognitive load and less surprises are probably an added value.
-- www.tudorgirba.com www.feenk.com "Every now and then stop and ask yourself if the war you're fighting is the right one."
Hi, i dont agree with this pov. for example consider this mixed-mode calc: aFraction ln + aNumber. Fraction>>#ln returns a float and i suppose you dont want to change that eg by returning a quasi-NaN like "ResultNonRepresentableInTheSetOfFractionsError". if you suppress (2) and aNumber would be a Float youd get a float as result. if aNumber would be a fraction youd get a fraction as result and perhaps somebody could expect the result to be precise, but actually it can be less precise than in the first case (two roundings instead of one). this change just opens a can of worms. werner On 11/12/2017 08:15 AM, Tudor Girba wrote:
Hi,
Indeed, I agree with this point of view.
Can we distill a concrete path to action from this?
Cheers, Doru
On Nov 11, 2017, at 11:58 AM, raffaello.giulietti@lifeware.ch wrote:
On 2017-11-10 22:18, Nicolas Cellier wrote:
2017-11-10 20:58 GMT+01:00 Martin McClure <martin@hand2mouse.com <mailto:martin@hand2mouse.com>>:
On 11/10/2017 11:33 AM, raffaello.giulietti@lifeware.ch <mailto:raffaello.giulietti@lifeware.ch> wrote:
Doing only Fraction->Float conversions in mixed mode won't preserve = as an equivalence relation and won't enable a consistent ordering with <=, which probably most Smalltalkers consider important and enjoyable properties.
Good point. I agree that Float -> Fraction is the more desirable mode for implicit conversion, since it can always be done without changing the value.
Nicolas gave some convincing examples on why most programmers might want to rely on them.
Also, as I mentioned, most Smalltalkers might prefer keeping away from the complex properties of Floats. Doing automatic, implicit Fraction->Float conversions behind the scenes only exacerbates the probability of encountering Floats and of having to deal with their weird and unfamiliar arithmetic.
One problem is that we make it easy to create Floats in source code (0.1), and we print Floats in a nice decimal format but by default print Fractions in their reduced fractional form. If we didn't do this, Smalltalkers might not be working with Floats in the first place, and if they did not have any Floats in their computation they would never run into an implicit conversion to *or* from Float.
As it is, if we were to uniformly do Float -> Fraction conversion on mixed-mode operations, we would get things like
(0.1 * (1/1)) printString --> '3602879701896397/36028797018963968'
Not incredibly friendly.
For those not practicing the litote: definitely a no go.
Regards, -Martin
At the risk of repeating myself, unique choice for all operations is a nice to have but not a goal per se.
I mostly agree with Florin: having 0.1 representing a decimal rather than a Float might be a better path meeting more expectations.
But thinking that it will magically eradicate the problems is a myth.
Shall precision be limited or shall we use ScaledDecimals ?
With limited precision, we'll be back to having several Fraction converting to same LimitedDecimal, so it won't solve anything wrt original problem. With illimted precision we'll have the bad property that what we print does not re-interpret to the same ScaledDecimal (0.1s / 0.3s), but that's a detail. The worse thing is that long chain of operations will tend to produce monster numerators denominators. And we will all have long chain when resizing a morph with proportional layout in scalable graphics. We will then have to insert rounding operations manually for mitigating the problem, and somehow reinvent a more inconvenient Float... It's boring to allways play the role of Cassandra, but why do you think that Scheme and Lisp did not choose that path?
I don't know why Lisper got this way. They justify the conversion choice for comparison (equivalence of = and total ordering of <=) but for the arithmetic operations there does not seem to be a clearly stated rationale.
Are they happy with their choice? I haven't the foggiest idea.
Don't get me wrong: I understand the Fraction->Float choice for the sake of more speed.
But this increase in speed is not functionally transparent: it is not like an increase in the clock rate of a CPU, it's not like a better performing division algorithm, a faster implementation of sorting or a JIT compiler.
This increase in speed, rather, comes at the cost of a shift in functionality and cognitive load, because even + or 0.1 have not their conventional semantics. It's not that Floats are wrong by themselves, it's that their operations seem weird at first and at odd with the familiar behavior.
Again, I'm not addressing the experts in floating point computation here: they know how to deal with it. Thus, I expect that the developers of numerically intensive libraries or packages, like 2D or 3D graphics, are fully aware of the trade-offs and are in a position to make an explicit choice: either monster denominators in fractions or more care with floats, but always in control.
Here, I'm targeting the John Doe Smalltalker which usually uses well-crafted, well-performing libraries and probably only performs a few thousand mixed Fraction/Float computations on his own, and then speed trade-offs do not matter. He is better served with implicit, automatic Float->Fraction conversions in both comparisons and operations.
For him, less cognitive load and less surprises are probably an added value. -- www.tudorgirba.com www.feenk.com
"Every now and then stop and ask yourself if the war you're fighting is the right one."
There is nice little Chapter about Float based on previous discussions with Nicolas :) In deep into pharo. Stef On Thu, Nov 9, 2017 at 3:50 PM, Nicolas Cellier <nicolas.cellier.aka.nice@gmail.com> wrote:
2017-11-09 15:44 GMT+01:00 Raffaello Giulietti <raffaello.giulietti@lifeware.ch>:
According to IEEE 754, the base of Pharo Float, *finite* values shall behave like old plain arithmetic.
This is out of context. There is no such thing as Fraction type covered by IEEE 754 standard.
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...
On 2017-11-09 15:36, Nicolas Cellier wrote:
Nope, not a bug.
If you use Float, then you have to know that (x -y) isZero and (x = y) are two different things. Example; Float infinity
In your case you want to protect against (x-y) isZero, so just do that.
2017-11-09 15:15 GMT+01:00 Tudor Girba <tudor@tudorgirba.com <mailto:tudor@tudorgirba.com>>:
Hi,
I just stumbled across this bug related to the equality between fraction and float:
https://pharo.fogbugz.com/f/cases/20488/x-y-iff-x-y-0-is-not-preserved-in-Ph...
<https://pharo.fogbugz.com/f/cases/20488/x-y-iff-x-y-0-is-not-preserved-in-Ph...>
In essence, the problem can be seen that by doing this, you get a ZeroDivide: x := 0.1. y := (1/10). x = y ifFalse: [ 1 / (x - y) ]
The issue seems to come from the Float being turned to a Fraction, rather than the Fraction being turned into a Float:
Fraction(Number)>>adaptToFloat: rcvr andCompare: selector "If I am involved in comparison with a Float, convert rcvr to a Fraction. This way, no bit is lost and comparison is exact."
rcvr isFinite ifFalse: [ selector == #= ifTrue: [^false]. selector == #~= ifTrue: [^true]. rcvr isNaN ifTrue: [^ false]. (selector = #< or: [selector = #'<=']) ifTrue: [^ rcvr positive not]. (selector = #> or: [selector = #'>=']) ifTrue: [^ rcvr positive]. ^self error: 'unknow comparison selector'].
^ *rcvr asTrueFraction perform: selector with: self*
Even if the comment says that the comparison is exact, to me this is a bug because it seems to fail doing that. What do you think?
Cheers, Doru
-- www.tudorgirba.com <http://www.tudorgirba.com> www.feenk.com <http://www.feenk.com>
"Problem solving should be focused on describing the problem in a way that makes the solution obvious."
Hi, Thanks for the answer. The example I provided was for convenience. I still do not understand why it is wrong to expect 0.1 = (1/10) to be true. Doru
On Nov 9, 2017, at 3:36 PM, Nicolas Cellier <nicolas.cellier.aka.nice@gmail.com> wrote:
Nope, not a bug.
If you use Float, then you have to know that (x -y) isZero and (x = y) are two different things. Example; Float infinity
In your case you want to protect against (x-y) isZero, so just do that.
2017-11-09 15:15 GMT+01:00 Tudor Girba <tudor@tudorgirba.com>: Hi,
I just stumbled across this bug related to the equality between fraction and float: https://pharo.fogbugz.com/f/cases/20488/x-y-iff-x-y-0-is-not-preserved-in-Ph...
In essence, the problem can be seen that by doing this, you get a ZeroDivide: x := 0.1. y := (1/10). x = y ifFalse: [ 1 / (x - y) ]
The issue seems to come from the Float being turned to a Fraction, rather than the Fraction being turned into a Float:
Fraction(Number)>>adaptToFloat: rcvr andCompare: selector "If I am involved in comparison with a Float, convert rcvr to a Fraction. This way, no bit is lost and comparison is exact."
rcvr isFinite ifFalse: [ selector == #= ifTrue: [^false]. selector == #~= ifTrue: [^true]. rcvr isNaN ifTrue: [^ false]. (selector = #< or: [selector = #'<=']) ifTrue: [^ rcvr positive not]. (selector = #> or: [selector = #'>=']) ifTrue: [^ rcvr positive]. ^self error: 'unknow comparison selector'].
^ rcvr asTrueFraction perform: selector with: self
Even if the comment says that the comparison is exact, to me this is a bug because it seems to fail doing that. What do you think?
Cheers, Doru
-- www.tudorgirba.com www.feenk.com
"Problem solving should be focused on describing the problem in a way that makes the solution obvious."
-- www.tudorgirba.com www.feenk.com "We are all great at making mistakes."
2017-11-09 15:48 GMT+01:00 Tudor Girba <tudor@tudorgirba.com>:
Hi,
Thanks for the answer. The example I provided was for convenience.
I still do not understand why it is wrong to expect 0.1 = (1/10) to be true.
Doru
Because there are infinitely many different Fraction that would be "equal" to 0.1 then. The first effect is that you have a = b a = c b < c You are breaking the fact that you can sort these Numbers (are they Magnitude anymore?) You are breaking the fact that you can mix these Numbers as Dictionary keys (sometimes the dictionary would have 2 elements, sometimes 3, unpredictably).
On Nov 9, 2017, at 3:36 PM, Nicolas Cellier <nicolas.cellier.aka.nice@ gmail.com> wrote:
Nope, not a bug.
If you use Float, then you have to know that (x -y) isZero and (x = y) are two different things. Example; Float infinity
In your case you want to protect against (x-y) isZero, so just do that.
2017-11-09 15:15 GMT+01:00 Tudor Girba <tudor@tudorgirba.com>: Hi,
I just stumbled across this bug related to the equality between fraction and float: https://pharo.fogbugz.com/f/cases/20488/x-y-iff-x-y-0-is- not-preserved-in-Pharo
In essence, the problem can be seen that by doing this, you get a ZeroDivide: x := 0.1. y := (1/10). x = y ifFalse: [ 1 / (x - y) ]
The issue seems to come from the Float being turned to a Fraction, rather than the Fraction being turned into a Float:
Fraction(Number)>>adaptToFloat: rcvr andCompare: selector "If I am involved in comparison with a Float, convert rcvr to a Fraction. This way, no bit is lost and comparison is exact."
rcvr isFinite ifFalse: [ selector == #= ifTrue: [^false]. selector == #~= ifTrue: [^true]. rcvr isNaN ifTrue: [^ false]. (selector = #< or: [selector = #'<=']) ifTrue: [^ rcvr positive not]. (selector = #> or: [selector = #'>=']) ifTrue: [^ rcvr positive]. ^self error: 'unknow comparison selector'].
^ rcvr asTrueFraction perform: selector with: self
Even if the comment says that the comparison is exact, to me this is a bug because it seems to fail doing that. What do you think?
Cheers, Doru
-- www.tudorgirba.com www.feenk.com
"Problem solving should be focused on describing the problem in a way that makes the solution obvious."
-- www.tudorgirba.com www.feenk.com
"We are all great at making mistakes."
Note that this started a long time ago and comes up episodically http://forum.world.st/Fraction-equality-and-Float- infinity-problem-td48323.html http://forum.world.st/BUG-Equality-should-be-transitive-tc1404335.html https://lists.gforge.inria.fr/pipermail/pharo-project/2009-July/010496.html A bit like a "marronnier" (in French, a subject that is treated periodically by newspapers and magazines) 2017-11-09 15:55 GMT+01:00 Nicolas Cellier < nicolas.cellier.aka.nice@gmail.com>:
2017-11-09 15:48 GMT+01:00 Tudor Girba <tudor@tudorgirba.com>:
Hi,
Thanks for the answer. The example I provided was for convenience.
I still do not understand why it is wrong to expect 0.1 = (1/10) to be true.
Doru
Because there are infinitely many different Fraction that would be "equal" to 0.1 then. The first effect is that you have
a = b a = c b < c
You are breaking the fact that you can sort these Numbers (are they Magnitude anymore?) You are breaking the fact that you can mix these Numbers as Dictionary keys (sometimes the dictionary would have 2 elements, sometimes 3, unpredictably).
On Nov 9, 2017, at 3:36 PM, Nicolas Cellier < nicolas.cellier.aka.nice@gmail.com> wrote:
Nope, not a bug.
If you use Float, then you have to know that (x -y) isZero and (x = y) are two different things. Example; Float infinity
In your case you want to protect against (x-y) isZero, so just do that.
2017-11-09 15:15 GMT+01:00 Tudor Girba <tudor@tudorgirba.com>: Hi,
I just stumbled across this bug related to the equality between fraction and float: https://pharo.fogbugz.com/f/cases/20488/x-y-iff-x-y-0-is-not -preserved-in-Pharo
In essence, the problem can be seen that by doing this, you get a ZeroDivide: x := 0.1. y := (1/10). x = y ifFalse: [ 1 / (x - y) ]
The issue seems to come from the Float being turned to a Fraction, rather than the Fraction being turned into a Float:
Fraction(Number)>>adaptToFloat: rcvr andCompare: selector "If I am involved in comparison with a Float, convert rcvr to a Fraction. This way, no bit is lost and comparison is exact."
rcvr isFinite ifFalse: [ selector == #= ifTrue: [^false]. selector == #~= ifTrue: [^true]. rcvr isNaN ifTrue: [^ false]. (selector = #< or: [selector = #'<=']) ifTrue: [^ rcvr positive not]. (selector = #> or: [selector = #'>=']) ifTrue: [^ rcvr positive]. ^self error: 'unknow comparison selector'].
^ rcvr asTrueFraction perform: selector with: self
Even if the comment says that the comparison is exact, to me this is a bug because it seems to fail doing that. What do you think?
Cheers, Doru
-- www.tudorgirba.com www.feenk.com
"Problem solving should be focused on describing the problem in a way that makes the solution obvious."
-- www.tudorgirba.com www.feenk.com
"We are all great at making mistakes."
On Thu, Nov 9, 2017 at 5:34 PM, Nicolas Cellier <nicolas.cellier.aka.nice@gmail.com> wrote:
Note that this started a long time ago and comes up episodically http://forum.world.st/Fraction-equality-and-Float-infinity-problem-td48323.h... http://forum.world.st/BUG-Equality-should-be-transitive-tc1404335.html https://lists.gforge.inria.fr/pipermail/pharo-project/2009-July/010496.html
A bit like a "marronnier" (in French, a subject that is treated periodically by newspapers and magazines)
Hi nicolas except that now we could a chapter describing some of the answers :)
On 2017-11-09 21:49, Stephane Ducasse wrote:
On Thu, Nov 9, 2017 at 5:34 PM, Nicolas Cellier <nicolas.cellier.aka.nice@gmail.com> wrote:
Note that this started a long time ago and comes up episodically http://forum.world.st/Fraction-equality-and-Float-infinity-problem-td48323.h... http://forum.world.st/BUG-Equality-should-be-transitive-tc1404335.html https://lists.gforge.inria.fr/pipermail/pharo-project/2009-July/010496.html
A bit like a "marronnier" (in French, a subject that is treated periodically by newspapers and magazines)
Hi nicolas except that now we could a chapter describing some of the answers :)
It's easy to make = behave as an equivalence if the two objects it operates on are of the same class. But it is well-known that it's hard when their classes are different, short of implementing = trivially in this case. This is to say that there's no real hope to make = an equivalence if the wish is to make it useful in the presence of different kind of objects. In the context of numeric computations, luckily, there is some hope when conversions, if possible at all, are all performed consistently. But the same conversions should then be applied for all operations, whether comparisons or subtractions, etc., to ensure more familiar properties. This is not currently the case in Pharo. Raffaello
raffaello.giulietti wrote
On 2017-11-09 21:49, Stephane Ducasse wrote:
On Thu, Nov 9, 2017 at 5:34 PM, Nicolas Cellier <
nicolas.cellier.aka.nice@
> wrote:
Note that this started a long time ago and comes up episodically http://forum.world.st/Fraction-equality-and-Float-infinity-problem-td48323.h... http://forum.world.st/BUG-Equality-should-be-transitive-tc1404335.html https://lists.gforge.inria.fr/pipermail/pharo-project/2009-July/010496.html
A bit like a "marronnier" (in French, a subject that is treated periodically by newspapers and magazines)
Hi nicolas except that now we could a chapter describing some of the answers :)
It's easy to make = behave as an equivalence if the two objects it operates on are of the same class.
But it is well-known that it's hard when their classes are different, short of implementing = trivially in this case.
This is to say that there's no real hope to make = an equivalence if the wish is to make it useful in the presence of different kind of objects.
In the context of numeric computations, luckily, there is some hope when conversions, if possible at all, are all performed consistently. But the same conversions should then be applied for all operations, whether comparisons or subtractions, etc., to ensure more familiar properties. This is not currently the case in Pharo.
Raffaello
Personally, I rather like = being transitive across different types of objects, which is the case with the current implementation.* It would not be if, like you suggest, multiple Fractions = the same Float. Take a moment to also reflect on what it would mean if, as the implication goes, a float represents a range of numbers on the rational number line, rather than a single point. Taken to its logical conclusion, it follows you'd need to also redefine the other mathematical operators to reflect this, as well as conversion to exact numbers like Integers and Fractions being lossy, rather than the other way around. You could certainly build an interesting system of floats with such a property (I can swear I've read a paper on one somewhere...), but it wouldn't be the world of IEEE754 Floats we live in. Other properties one might deem beneficial, such as a + b > a if b > 0, or (a + b) + c = a + (b + c) are not true in the context of floats. Does that mean we need to fix them too? My 2c: It feels like you are asking Floats to be something they're not, and the answer simply isn't to try and paint over issues and try and make them look like something they're not. Cheers, Henry -- Sent from: http://forum.world.st/Pharo-Smalltalk-Developers-f1294837.html
On 2017-11-10 00:02, Henrik Sperre Johansen wrote:
raffaello.giulietti wrote
On 2017-11-09 21:49, Stephane Ducasse wrote:
On Thu, Nov 9, 2017 at 5:34 PM, Nicolas Cellier <
nicolas.cellier.aka.nice@
> wrote:
Note that this started a long time ago and comes up episodically http://forum.world.st/Fraction-equality-and-Float-infinity-problem-td48323.h... http://forum.world.st/BUG-Equality-should-be-transitive-tc1404335.html https://lists.gforge.inria.fr/pipermail/pharo-project/2009-July/010496.html
A bit like a "marronnier" (in French, a subject that is treated periodically by newspapers and magazines)
Hi nicolas except that now we could a chapter describing some of the answers :)
It's easy to make = behave as an equivalence if the two objects it operates on are of the same class.
But it is well-known that it's hard when their classes are different, short of implementing = trivially in this case.
This is to say that there's no real hope to make = an equivalence if the wish is to make it useful in the presence of different kind of objects.
In the context of numeric computations, luckily, there is some hope when conversions, if possible at all, are all performed consistently. But the same conversions should then be applied for all operations, whether comparisons or subtractions, etc., to ensure more familiar properties. This is not currently the case in Pharo.
Raffaello
Personally, I rather like = being transitive across different types of objects, which is the case with the current implementation.* It would not be if, like you suggest, multiple Fractions = the same Float. Take a moment to also reflect on what it would mean if, as the implication goes, a float represents a range of numbers on the rational number line, rather than a single point. Taken to its logical conclusion, it follows you'd need to also redefine the other mathematical operators to reflect this, as well as conversion to exact numbers like Integers and Fractions being lossy, rather than the other way around. You could certainly build an interesting system of floats with such a property (I can swear I've read a paper on one somewhere...), but it wouldn't be the world of IEEE754 Floats we live in.
Other properties one might deem beneficial, such as a + b > a if b > 0, or (a + b) + c = a + (b + c) are not true in the context of floats. Does that mean we need to fix them too? My 2c: It feels like you are asking Floats to be something they're not, and the answer simply isn't to try and paint over issues and try and make them look like something they're not.
Cheers, Henry
Personally, I could live without mixed arithmetic between unlimited and limited precision numbers: I would always be explicit in the conversions. They are so different in nature, like apples and oranges are. This also entails that I would accept that 1 = 1.0 evaluates to false, that 1 <= 1.0 throws an exception and all consequences of this. But this would be unacceptable for most Smalltalkers for many good reasons. And, as you point out, = and total ordering are easily preserved if Floats are converted to Fractions. But then please convert Floats to Fractions even when adding, multiplying, etc. To me a Float does not stand for an interval. It's just a real number that happens to have strange, unfamiliar operations that resemble the pure ones. Some familiar properties also hold for these strange operations, others do not.
2017-11-10 1:18 GMT+01:00 <raffaello.giulietti@lifeware.ch>:
On 2017-11-10 00:02, Henrik Sperre Johansen wrote:
raffaello.giulietti wrote
On 2017-11-09 21:49, Stephane Ducasse wrote:
On Thu, Nov 9, 2017 at 5:34 PM, Nicolas Cellier <
nicolas.cellier.aka.nice@
> wrote:
Note that this started a long time ago and comes up episodically http://forum.world.st/Fraction-equality-and-Float- infinity-problem-td48323.html http://forum.world.st/BUG-Equality-should-be-transitive- tc1404335.html https://lists.gforge.inria.fr/pipermail/pharo-project/2009- July/010496.html
A bit like a "marronnier" (in French, a subject that is treated periodically by newspapers and magazines)
Hi nicolas except that now we could a chapter describing some of the answers :)
It's easy to make = behave as an equivalence if the two objects it operates on are of the same class.
But it is well-known that it's hard when their classes are different, short of implementing = trivially in this case.
This is to say that there's no real hope to make = an equivalence if the wish is to make it useful in the presence of different kind of objects.
In the context of numeric computations, luckily, there is some hope when conversions, if possible at all, are all performed consistently. But the same conversions should then be applied for all operations, whether comparisons or subtractions, etc., to ensure more familiar properties. This is not currently the case in Pharo.
Raffaello
Personally, I rather like = being transitive across different types of objects, which is the case with the current implementation.* It would not be if, like you suggest, multiple Fractions = the same Float. Take a moment to also reflect on what it would mean if, as the implication goes, a float represents a range of numbers on the rational number line, rather than a single point. Taken to its logical conclusion, it follows you'd need to also redefine the other mathematical operators to reflect this, as well as conversion to exact numbers like Integers and Fractions being lossy, rather than the other way around. You could certainly build an interesting system of floats with such a property (I can swear I've read a paper on one somewhere...), but it wouldn't be the world of IEEE754 Floats we live in.
Other properties one might deem beneficial, such as a + b > a if b > 0, or (a + b) + c = a + (b + c) are not true in the context of floats. Does that mean we need to fix them too? My 2c: It feels like you are asking Floats to be something they're not, and the answer simply isn't to try and paint over issues and try and make them look like something they're not.
Cheers, Henry
Personally, I could live without mixed arithmetic between unlimited and limited precision numbers: I would always be explicit in the conversions. They are so different in nature, like apples and oranges are.
This also entails that I would accept that 1 = 1.0 evaluates to false, that 1 <= 1.0 throws an exception and all consequences of this.
But this would be unacceptable for most Smalltalkers for many good reasons. And, as you point out, = and total ordering are easily preserved if Floats are converted to Fractions. But then please convert Floats to Fractions even when adding, multiplying, etc.
To me a Float does not stand for an interval. It's just a real number that happens to have strange, unfamiliar operations that resemble the pure ones. Some familiar properties also hold for these strange operations, others do not.
It would be tempting, but a priori I don't believe it would be sustainable. What is nice in Smalltalk is that we can just try. (a bit less easy for int+float because it's hardwired in the primitives and maybe the JIT too)
So in current Pharo, if I just define Fraction>>adaptToFloat: f andSend: m ^f isFinite ifTrue: [f asFraction perform: m with: self] ifFalse: [f perform: m with: self asFloat] and symmetric in Float>>adaptToFraction: f andSend: m ^self isFinite ifTrue: [f perform: m with: self asFraction] ifFalse: [f asFloat perform: m with: self] then World does not seem to fall apart... We would need to measure if noticeable slow down happens
2017-11-10 0:02 GMT+01:00 Henrik Sperre Johansen < henrik.s.johansen@veloxit.no>:
raffaello.giulietti wrote
On 2017-11-09 21:49, Stephane Ducasse wrote:
On Thu, Nov 9, 2017 at 5:34 PM, Nicolas Cellier <
nicolas.cellier.aka.nice@
> wrote:
Note that this started a long time ago and comes up episodically http://forum.world.st/Fraction-equality-and-Float-infinity- problem-td48323.html http://forum.world.st/BUG-Equality-should-be-transitive-tc1404335.html https://lists.gforge.inria.fr/pipermail/pharo-project/2009-J uly/010496.html
A bit like a "marronnier" (in French, a subject that is treated periodically by newspapers and magazines)
Hi nicolas except that now we could a chapter describing some of the answers :)
It's easy to make = behave as an equivalence if the two objects it operates on are of the same class.
But it is well-known that it's hard when their classes are different, short of implementing = trivially in this case.
This is to say that there's no real hope to make = an equivalence if the wish is to make it useful in the presence of different kind of objects.
In the context of numeric computations, luckily, there is some hope when conversions, if possible at all, are all performed consistently. But the same conversions should then be applied for all operations, whether comparisons or subtractions, etc., to ensure more familiar properties. This is not currently the case in Pharo.
Raffaello
Personally, I rather like = being transitive across different types of objects, which is the case with the current implementation.* It would not be if, like you suggest, multiple Fractions = the same Float. Take a moment to also reflect on what it would mean if, as the implication goes, a float represents a range of numbers on the rational number line, rather than a single point. Taken to its logical conclusion, it follows you'd need to also redefine the other mathematical operators to reflect this, as well as conversion to exact numbers like Integers and Fractions being lossy, rather than the other way around. You could certainly build an interesting system of floats with such a property (I can swear I've read a paper on one somewhere...), but it wouldn't be the world of IEEE754 Floats we live in.
Other properties one might deem beneficial, such as a + b > a if b > 0, or (a + b) + c = a + (b + c) are not true in the context of floats. Does that mean we need to fix them too? My 2c: It feels like you are asking Floats to be something they're not, and the answer simply isn't to try and paint over issues and try and make them look like something they're not.
Cheers, Henry
on the other hand, his original expectations are true when restricted in the world of floating point: (a isFloat & b isFloat & a isFinite & b isFinite) ==> (a-b)=0 = (a=b) so the astonishment is legitimate It's just that we have a more complex model in which above Smalltalk expression cannot hold with mixed arithmetic without breaking higher expectations...
-- Sent from: http://forum.world.st/Pharo-Smalltalk-Developers-f1294837.html
On 2017-11-09 15:55, Nicolas Cellier wrote:
2017-11-09 15:48 GMT+01:00 Tudor Girba <tudor@tudorgirba.com <mailto:tudor@tudorgirba.com>>:
Hi,
Thanks for the answer. The example I provided was for convenience.
I still do not understand why it is wrong to expect 0.1 = (1/10) to be true.
Doru
Because there are infinitely many different Fraction that would be "equal" to 0.1 then. The first effect is that you have
a = b a = c b < c
You are breaking the fact that you can sort these Numbers (are they Magnitude anymore?) You are breaking the fact that you can mix these Numbers as Dictionary keys (sometimes the dictionary would have 2 elements, sometimes 3, unpredictably).
Fractions are not reliable keys anyway: (Fraction numerator: 1 denominator: 3) = (Fraction numerator: 2 denominator: 6) evaluates to true while (Fraction numerator: 1 denominator: 3) hash = (Fraction numerator: 2 denominator: 6) hash evaluates to false
2017-11-09 18:11 GMT+01:00 Raffaello Giulietti < raffaello.giulietti@lifeware.ch>:
On 2017-11-09 15:55, Nicolas Cellier wrote:
2017-11-09 15:48 GMT+01:00 Tudor Girba <tudor@tudorgirba.com <mailto: tudor@tudorgirba.com>>:
Hi,
Thanks for the answer. The example I provided was for convenience.
I still do not understand why it is wrong to expect 0.1 = (1/10) to be true.
Doru
Because there are infinitely many different Fraction that would be "equal" to 0.1 then. The first effect is that you have
a = b a = c b < c
You are breaking the fact that you can sort these Numbers (are they Magnitude anymore?) You are breaking the fact that you can mix these Numbers as Dictionary keys (sometimes the dictionary would have 2 elements, sometimes 3, unpredictably).
Fractions are not reliable keys anyway: (Fraction numerator: 1 denominator: 3) = (Fraction numerator: 2 denominator: 6) evaluates to true while (Fraction numerator: 1 denominator: 3) hash = (Fraction numerator: 2 denominator: 6) hash evaluates to false
You are violating the invariants described in class comment in this case, and thus missusing Fraction. So it's not anymore the problem of the library
ASN.1 encoding is a long standing encoding format and includes accepted encoding of Reals and Integers. I read this thread and decided to see what difference that ASN.1 Real encoding may report between this float and the fraction. It turns out it is the same, but not in an equality test with the fraction. That is due to conversion from Fraction to Float before encoding. Here is the results I found: ASN1OutputStream encode: (1/10). ASN1OutputStream encode: (0.1). bytes := #[9 9 128 201 12 204 204 204 204 204 205] This breaks down as in: ASN1InputStream decodeBytes: bytes. obj := (3602879701896397/36028797018963968) Which equals 0.1, but does not equal (1/10). Food for thought regarding ASN.1 encoding of Reals. - HH
-------- Original Message -------- Subject: Re: [Pharo-dev] float & fraction equality bug Local Time: November 9, 2017 9:48 AM UTC Time: November 9, 2017 2:48 PM From: tudor@tudorgirba.com To: Pharo Development List <pharo-dev@lists.pharo.org>
Hi,
Thanks for the answer. The example I provided was for convenience.
I still do not understand why it is wrong to expect 0.1 = (1/10) to be true.
Doru
On Nov 9, 2017, at 3:36 PM, Nicolas Cellier nicolas.cellier.aka.nice@gmail.com wrote: Nope, not a bug. If you use Float, then you have to know that (x -y) isZero and (x = y) are two different things. Example; Float infinity In your case you want to protect against (x-y) isZero, so just do that. 2017-11-09 15:15 GMT+01:00 Tudor Girba tudor@tudorgirba.com: Hi, I just stumbled across this bug related to the equality between fraction and float: https://pharo.fogbugz.com/f/cases/20488/x-y-iff-x-y-0-is-not-preserved-in-Ph... In essence, the problem can be seen that by doing this, you get a ZeroDivide: x := 0.1. y := (1/10). x = y ifFalse: [ 1 / (x - y) ] The issue seems to come from the Float being turned to a Fraction, rather than the Fraction being turned into a Float: Fraction(Number)>>adaptToFloat: rcvr andCompare: selector "If I am involved in comparison with a Float, convert rcvr to a Fraction. This way, no bit is lost and comparison is exact." rcvr isFinite ifFalse: [ selector == #= ifTrue: [^false]. selector == #~= ifTrue: [^true]. rcvr isNaN ifTrue: [^ false]. (selector = #< or: [selector = #'<=']) ifTrue: [^ rcvr positive not]. (selector = #> or: [selector = #'>=']) ifTrue: [^ rcvr positive]. ^self error: 'unknow comparison selector']. ^ rcvr asTrueFraction perform: selector with: self Even if the comment says that the comparison is exact, to me this is a bug because it seems to fail doing that. What do you think? Cheers, Doru -- www.tudorgirba.com www.feenk.com "Problem solving should be focused on describing the problem in a way that makes the solution obvious."
www.tudorgirba.com www.feenk.com
"We are all great at making mistakes."
To break down the ASN.1 encoding of 0.1 we have the following: sign * mantissa * (2 raisedTo: scalingFactor) * (base raisedTo: exponent). And in this case the values for 0.1 are as follows: self assert: sign = 1. self assert: mantissa = 3602879701896397. self assert: scalingFactor = 0. self assert: base = 2. self assert: exponent = -55. Just to close it up... - HH
-------- Original Message -------- Subject: Re: [Pharo-dev] float & fraction equality bug Local Time: November 9, 2017 10:13 AM UTC Time: November 9, 2017 3:13 PM From: henry@callistohouse.club To: Pharo Development List <pharo-dev@lists.pharo.org>
ASN.1 encoding is a long standing encoding format and includes accepted encoding of Reals and Integers. I read this thread and decided to see what difference that ASN.1 Real encoding may report between this float and the fraction. It turns out it is the same, but not in an equality test with the fraction. That is due to conversion from Fraction to Float before encoding. Here is the results I found:
ASN1OutputStream encode: (1/10). ASN1OutputStream encode: (0.1). bytes := #[9 9 128 201 12 204 204 204 204 204 205]
This breaks down as in: ASN1InputStream decodeBytes: bytes. obj := (3602879701896397/36028797018963968)
Which equals 0.1, but does not equal (1/10).
Food for thought regarding ASN.1 encoding of Reals.
- HH
-------- Original Message -------- Subject: Re: [Pharo-dev] float & fraction equality bug Local Time: November 9, 2017 9:48 AM UTC Time: November 9, 2017 2:48 PM From: tudor@tudorgirba.com To: Pharo Development List <pharo-dev@lists.pharo.org>
Hi,
Thanks for the answer. The example I provided was for convenience.
I still do not understand why it is wrong to expect 0.1 = (1/10) to be true.
Doru
On Nov 9, 2017, at 3:36 PM, Nicolas Cellier nicolas.cellier.aka.nice@gmail.com wrote: Nope, not a bug. If you use Float, then you have to know that (x -y) isZero and (x = y) are two different things. Example; Float infinity In your case you want to protect against (x-y) isZero, so just do that. 2017-11-09 15:15 GMT+01:00 Tudor Girba tudor@tudorgirba.com: Hi, I just stumbled across this bug related to the equality between fraction and float: https://pharo.fogbugz.com/f/cases/20488/x-y-iff-x-y-0-is-not-preserved-in-Ph... In essence, the problem can be seen that by doing this, you get a ZeroDivide: x := 0.1. y := (1/10). x = y ifFalse: [ 1 / (x - y) ] The issue seems to come from the Float being turned to a Fraction, rather than the Fraction being turned into a Float: Fraction(Number)>>adaptToFloat: rcvr andCompare: selector "If I am involved in comparison with a Float, convert rcvr to a Fraction. This way, no bit is lost and comparison is exact." rcvr isFinite ifFalse: [ selector == #= ifTrue: [^false]. selector == #~= ifTrue: [^true]. rcvr isNaN ifTrue: [^ false]. (selector = #< or: [selector = #'<=']) ifTrue: [^ rcvr positive not]. (selector = #> or: [selector = #'>=']) ifTrue: [^ rcvr positive]. ^self error: 'unknow comparison selector']. ^ rcvr asTrueFraction perform: selector with: self Even if the comment says that the comparison is exact, to me this is a bug because it seems to fail doing that. What do you think? Cheers, Doru -- www.tudorgirba.com www.feenk.com "Problem solving should be focused on describing the problem in a way that makes the solution obvious."
www.tudorgirba.com www.feenk.com
"We are all great at making mistakes."
Because by definition, a floating point value is of the form +/- 2^e * m for some 0 <= m < M, and e in some range of values (some positive, some negative). There are other special cases that don't matter here, such as NaN, INF, denormals, etc. So now set up the equality you want. Since it's positive we can skip the sign. Hence, 2^e * m = 1/10 Or, rather, 10 * 2^e * m = 1 The Fundamental Theorem of Arithmetic shows this is impossible. Andres. On 11/9/17 6:48 , Tudor Girba wrote:
Hi,
Thanks for the answer. The example I provided was for convenience.
I still do not understand why it is wrong to expect 0.1 = (1/10) to be true.
Doru
On Nov 9, 2017, at 3:36 PM, Nicolas Cellier <nicolas.cellier.aka.nice@gmail.com> wrote:
Nope, not a bug.
If you use Float, then you have to know that (x -y) isZero and (x = y) are two different things. Example; Float infinity
In your case you want to protect against (x-y) isZero, so just do that.
2017-11-09 15:15 GMT+01:00 Tudor Girba <tudor@tudorgirba.com>: Hi,
I just stumbled across this bug related to the equality between fraction and float: https://pharo.fogbugz.com/f/cases/20488/x-y-iff-x-y-0-is-not-preserved-in-Ph...
In essence, the problem can be seen that by doing this, you get a ZeroDivide: x := 0.1. y := (1/10). x = y ifFalse: [ 1 / (x - y) ]
The issue seems to come from the Float being turned to a Fraction, rather than the Fraction being turned into a Float:
Fraction(Number)>>adaptToFloat: rcvr andCompare: selector "If I am involved in comparison with a Float, convert rcvr to a Fraction. This way, no bit is lost and comparison is exact."
rcvr isFinite ifFalse: [ selector == #= ifTrue: [^false]. selector == #~= ifTrue: [^true]. rcvr isNaN ifTrue: [^ false]. (selector = #< or: [selector = #'<=']) ifTrue: [^ rcvr positive not]. (selector = #> or: [selector = #'>=']) ifTrue: [^ rcvr positive]. ^self error: 'unknow comparison selector'].
^ rcvr asTrueFraction perform: selector with: self
Even if the comment says that the comparison is exact, to me this is a bug because it seems to fail doing that. What do you think?
Cheers, Doru
-- www.tudorgirba.com www.feenk.com
"Problem solving should be focused on describing the problem in a way that makes the solution obvious."
-- www.tudorgirba.com www.feenk.com
"We are all great at making mistakes."
.
participants (12)
-
Andres Valloud -
Florin Mateoc -
Henrik Sperre Johansen -
henry -
Martin McClure -
Nicolas Cellier -
Raffaello Giulietti -
raffaello.giulietti@lifeware.ch -
Stephane Ducasse -
Sven Van Caekenberghe -
Tudor Girba -
werner kassens