talman has asked for the wisdom of the Perl Monks concerning the following question:
Produces the output: CRAPperl -e 'print "CRAP\n" if ((33.4+33.3+33.3) != 100)'
However:
Does not produce an output.perl -e 'print "CRAP\n" if ((33.3+33.3+33.4) != 100)'
Another example is: 85.8+7.1+7.1 (7.1+7.1+85.8 == 100)
And btw I did notice that when it doesn't "equal" 100, it a very high fraction of 99.999..., but why is a certain order and only certain numbers causing this.
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re: Funny addition behaviour
by tachyon (Chancellor) on Apr 22, 2004 at 08:30 UTC | |
cheers tachyon | [reply] |
|
Re: Funny addition behaviour
by matija (Priest) on Apr 22, 2004 at 08:24 UTC | |
Floating point numbers at the limit of precision are funny - order of arithmetic operations definitely makes a difference, and errors grow as you perform more consecutive operations. Which is why, in any serious book devoted to mathematical simulation (which involves numerous floating point operations) you will find, not equality comparisons, but "error bar" comparions: abs($val-$expected)<0.0001 vs $val == $expected (adjust the 0.0001 value as required/possible). | [reply] [d/l] [select] |
|
Re: Funny addition behaviour
by TilRMan (Friar) on Apr 22, 2004 at 08:44 UTC | |
| [reply] | |
|
Re: Funny addition behaviour
by fizbin (Chaplain) on Apr 23, 2004 at 03:09 UTC | |
Several times below I abuse the word "digit" to refer to a single character in any number system, not just a base-ten one. The meaning should be clear from context. To see what's really going on at a bit-wise level, you can look at this "one-liner":
and give it the arguments:
The unpack/pack combination gives you the floating point numbers as perl sees them ("F" means "what perl uses for floating point" - on my machine, this is a double, so I could have gotten the same results with "d"), and the reverse is just there because intel is a little-endian architecture, and (modern) human beings are used to reading numbers with the most significant digit on the left. You get:
(some spaces manually trimmed to fit all on one line in perlmonks.org's code display) Now, what this means is not too hard to decode; here's a rough translation: (numbers prefixed with a 'B' mean binary)
So now let's do the math step-by-step: ('B' omitted; you should now by now what stuff's binary and what stuff isn't) So now we need to get tricky. The last two additions involve one addend that's represented as 1.something times 2^6, and another addend that's represented as 1.something times 2^5. Before adding them, the addend that's represented as 1.something times 2^5 has to be rewritten so that it's 0.1something times 2^6. Then the addition can proceed normally. Note that this rewriting may involve some rounding of the last few binary digits.
Now, if you're sharp-eyed, you'll notice that when we rounded the result of 33.4 + 33.3 to fit into a double, we rounded down (the last few digits went from "11001" to "1100"). However, when we were rewriting 33.4 so that it was something times 2^6, we rounded upwards (the last few digits went from "10011" to "1010"). This is a demonstration of the fact that my hardware uses a round-to-even strategy when rounding floating point numbers - if the digit being chopped off is a "1", it rounds to the nearest number that makes the rounded result end in "0". (when humans use this strategy on decimal numbers, it affects what they do when they're rounding off a "5" - in grade school, we were taught to always round up with a "5", but many places use the rule "when rounding off a 5, round either up or down so that the new last digit is even") In most cases, round-to-even means that round-off errors tend to cancel each other out, so it really is generally the most sensible strategy. However, people who are heavily into numerical analysis will want the ability to tweak this behavior for certain applications, and in fact there are functions defined in the standard math.h C header to do just this. Anyway, the error in general is that floating point math on a computer is done in binary, not in decimal, and some things which are exactly representable in decimal aren't exactly representable in binary, so roundoff errors can creep in where one might not expect them. (but not the other way around - anything representable exactly in binary is representable exactly in decimal, which makes me think that perhaps some computer should be invented that internally handles floating point stuff in base 210, since that way you get everything decimal can handle, and also 1/3 and 1/7. 1/11 still gives you trouble, but to handle that too you'd need more than one byte per digit, which might make things harder...) | [reply] [d/l] [select] |
by fizbin (Chaplain) on Apr 23, 2004 at 03:17 UTC | |
Assuming your perl uses the same size floating point numbers as mine, the first three hex digits encode the sign and exponent, and the last 13 hex digits encode the part of the mantissa after "1." | [reply] [d/l] [select] |
|
Re: Funny addition behaviour
by Anonymous Monk on Apr 22, 2004 at 10:14 UTC | |
Most annoying is that I thought of floating point inaccuracy, but because I tried the second combination as a test which worked, I had to go back and slowly eliminate code. It's not every day that you find out, (33.3+33.3+33.4) != (33.4+33.3+33.3). I think I won most obscure bug in the office. | [reply] |
|
Re: Funny addition behaviour
by Theo (Priest) on Apr 22, 2004 at 23:55 UTC | |
In other words, can you get around the problem by not dealing with decimals? -Theo- | [reply] [d/l] |