# rounding bug?

Hi all:

In a notebook window i tell Sage

w = 0.4
while w > 0.1:
w = w - 0.1
print w
print w==0


and it outputs

0.3000000000000000
0.200000000000000
0.100000000000000
2.77555756156289e-17
False


Is this supposed to happen?

edit retag close merge delete

Sort by » oldest newest most voted Yep, that's actually what's supposed to happen, because of how floating-point arithmetic works. Here's a gentle guide to the issues. Using comparisons with the results of arithmetic operations is always a bit dangerous because of limited precision.. note that the final error you get is ~2^(-55).

Your number "0.1" is living in the Real Field with 53 bits of precision:

sage: w = 0.4
sage: w
0.400000000000000
sage: parent(w)
Real Field with 53 bits of precision


and so the number isn't actually exactly 0.4:

sage: (0.4).str(base=2)
'0.011001100110011001100110011001100110011001100110011010'


You can round the result to a given number of decimal digits if you want:

sage: w = 0.4
sage: dw = 0.1
sage: ((((w - dw)-dw)-dw)-dw)
2.77555756156289e-17
sage: round(((((w - dw)-dw)-dw)-dw), ndigits=10)
0.0


Or use more precision:

sage: RF = RealField(100)
sage: RF
Real Field with 100 bits of precision
sage: w = RF("0.4")
sage: w.str(base=2)
'0.01100110011001100110011001100110011001100110011001100110011001100110011001100110011001100110011001101'
sage: dw = RF("0.1")
sage: while w > dw:
....:         w = w - dw
....:     print w
....:
0.30000000000000000000000000000
0.20000000000000000000000000000
0.10000000000000000000000000000
1.9721522630525295135293214132e-31
sage: print w==0
False


but that will only make the error smaller, in this case ~2^(-102), not vanish. You can use the Real Interval Field if you want to more accurately keep track of these differences:

sage: w = RIF("0.4")
sage: dw = RIF("0.1")
sage: while w > dw:
....:         w = w - dw
....:     print w
....:
0.3000000000000000?
0.2000000000000000?
0.1000000000000000?


where the question mark indicates uncertainty. But now the loop doesn't go all the way down to zero, because on the last step of the loop it's no longer true that 0.10000.. > 0.10000.. (See what I mean about trouble comparing?) Once you get two zeros, though, you can compare them:

sage: w = RIF("0.4")
sage: dw = RIF("0.1")
sage: ((((w - dw)-dw)-dw)-dw)
0.?e-16
sage: z = ((((w - dw)-dw)-dw)-dw)
sage: z == 0 # it's not _equal_ to 0, but:
False
sage: 0 in z # 0 is in the interval!
True


And if you really want 4/10, and not a floating-point approximation to it, you can work with the exact values:

sage: w = 4/10
sage: dw = 1/10
sage: while w >= dw:
....:         w = w - dw
....:     print w
....:
3/10
1/5
1/10
0
sage: w == 0
True


and avoid precision issues. Does that make sense?

more

In a word, yes. You are using floating point accuracy, which means that 0.4 is not actually 0.4, but rather the closest the computer can get to it with a certain accuracy. In Sage, that default is 53 bits of precision:

sage: w.parent()
Real Field with 53 bits of precision
sage: w.str(truncate=False)
'0.40000000000000002'


For what you are looking for, you need arbitrary precision - which rationals would give you.

sage: w = 4/10
sage: while w > 1/10:
....:     w = w - 1/10
....:     print w
....:
3/10
1/5
1/10


which also shows that your loop 'should have' ended earlier, but didn't because the number that printed 0.10000 actually was slightly larger than 0.1 in the computer's internal memory, so the loop iterated one more time.

I'm not an expert on numerical analysis or machine representation of numbers, but the Internet should have some good explanations of these things.

more

As a further comment, pretty much any computer mathematics software is going to have this limitation. Some have only this type of representation, while others have both exact and various precision number types.

@kcrisman: I promise I didn't see yours before writing mine, so any similarity is merely driven by the subject matter. :^)

Obviously! I've actually learned quite a bit by reading up on this over the past year or so; it's very interesting. Not as good as algebra, but at least numerical analysis has some redeeming value after all.