7

So this is weird. I'm in Ruby 1.9.3, and float addition is not working as I expect it would.

0.3 + 0.6 + 0.1 = 0.9999999999999999
0.6 + 0.1 + 0.3 = 1

I've tried this on another machine and get the same result. Any idea why this might happen?

2
  • 2
    floating-point-gui.de Yes, floating point numbers can do that. None of those are exactly representable.
    – Qsario
    Commented Dec 12, 2012 at 20:21
  • 1
    This question was closed inappropriately because the purported duplicate does not address associativity, and this question does. Floating-point arithmetic has many features, and addressing all questions about floating-point by saying “It is inaccurate, live with it” is grossly inadequate. Commented Dec 13, 2012 at 17:32

5 Answers 5

13

Floating point operations are inexact: they round the result to nearest representable float value.
That means that each float operation is:

float(a op b) = mathematical(a op b) + rounding-error( a op b )

As suggested by above equation, the rounding error depends on operands a & b.
Thus, if you perform operations in different order,

float(float( a op b) op c) != float(a op (b op c))

In other words, floating point operations are not associative.
They are commutative though...

As other said, transforming a decimal representation 0.1 (that is 1/10) into a base 2 representation (that is 1/16 + 1/64 + ... ) would lead to an infinite serie of digits. So float(0.1) is not equal to 1/10 exactly, it also has a rounding-error and it leads to a long serie of binary digits, which explains that following operations have a non null rounding-error (mathematical result is not representable in floating point)

2
  • Thanks for the great answer - I'll make sure to do some more reading on this.
    – Marc
    Commented Dec 12, 2012 at 23:31
  • Thanks, if not exact, float operations are deterministic at least, unfortunately my mental operations not so, 1/10 is rather 1/16+1/32+1/256+1/512+...
    – aka.nice
    Commented Dec 13, 2012 at 10:26
7

It has been said many times before but it bears repeating: Floating point numbers are by their very nature approximations of decimal numbers. There are some decimal numbers that cannot be represented precisely due to the way the floating point numbers are stored in binary. Small but perceptible rounding errors will occur.

To avoid this kind of mess, you should always format your numbers to an appropriate number of places for presentation:

 '%.3f' % (0.3 + 0.6 + 0.1)
 # => "1.000" 

 '%.3f' % (0.6 + 0.1 + 0.3)
 # => "1.000" 

This is why using floating point numbers for currency values is risky and you're generally encouraged to use fixed point numbers or regular integers for these things.

6
  • 3
    This explains why case 1 was inaccurate, but not why the order made a difference. Commented Dec 12, 2012 at 20:34
  • 1
    Back in the day, I helped write a financial software package that used floating point numbers for currency. You have no idea the problems it caused. Commented Dec 12, 2012 at 21:07
  • As for order, it's just a crap shoot. Stop expecting exact answers when using floating point; you'll be a happier, more relaxed person. Commented Dec 12, 2012 at 21:09
  • 1
    Folks, please do not vote for an answer just because it says floating-point is approximate. Vote for an answer if it actually explains the behavior asked about in the question. As Cthulhu says, this answer does not explain why adding in a different order produces different results. There are floating-point results which are inaccurate but which do not depend on order, for certain inputs. aka.nice’s answer is better because it explains why associativity does not hold. Commented Dec 12, 2012 at 21:09
  • The internet already has hundreds of in-depth explanations of how floats work internally. I don't know why giving a summary is a bad thing. If one is truly curious, there are many resources out there to explain in as much detail as you desire.
    – tadman
    Commented Dec 12, 2012 at 21:43
4

First, the numerals “0.3”, “.6”, and “.1” in the source text are converted to floating-point numbers, which I will call a, b, and c. These values are near .3, .6, and .1 but not equal to them, but that is not directly the reason you see different results.

In each floating-point arithmetic operation, there may be a little rounding error, some small number ei. So the exact mathematical results your two expressions calculate is:

(a + b + e0) + c + e1 and (b + c + e2) + a + e3.

That is, in the first expression, a is added to b, and there is a slight rounding error e0. Then c is added, and there is a slight rounding error e1. In the second expression, b is added to c, and there is a slight rounding error e2. Finally, a is added, and there is a slight rounding error e3.

The reason your results differ is that e0 + e1e2 + e3. That is, the rounding that was necessary when a and b were added was different from the rounding that was necessary when b and c were added and/or the roundings that were necessary in the second additions of the two cases were different.

There are rules that govern these errors. If you know the rules, you can make deductions about them that bound the size of the errors in final results.

2

This is a common limitation of floating point numbers, due to their being encoded in base 2 instead of base 10. It can be difficult to understand, but once you do, you can easily avoid problems like this. I recommend this guide, which goes in depth to explain it.

For this problem specifically, you might try rounding your result to the nearest millionths place:

result = (0.3+0.6+0.1)
  => 0.9999999999999999
(result*1000000.0).round/1000000.0
  => 1.0

As for why the order matters, it has to do with rounding. When those numbers are turned into floats, they are converted to binary, and all of them become repeating fractions, like ⅓ is in decimal. Since the result gets rounded during each addition, the final answer depends on the order of the additions. It appears that in one of those, you get a round-up, where in the other, you get a round-down. This explains the discrepancy.

It is worth noting what the actual difference is between those two answers: approximately 0.0000000000000001.

1
  • 1
    I suggest to use result.round 6 for an equivalent result with less, more readable, code
    – Rorrim
    Commented Jun 10, 2021 at 8:43
0

In view you can use also the number_with_precision helper:

result = 0.3 + 0.6 + 0.1
result = number_with_precision result, :precision => 3
1
  • 2
    Down vote because the question is about ruby and number_with_precision is a Ruby on Rails method. Commented May 2, 2020 at 12:44

Not the answer you're looking for? Browse other questions tagged or ask your own question.