views:

80

answers:

2

I am new to python and I was writing something like:

t = 0.  
while t<4.9:  
    t = t + 0.1  
    if t == 1.:
        ... do something ...

I noticed that the if statement was never being executed. So I modified the code to look like this:

''' Case a'''
t = 0.  
while t<4.9:  
    t = t + 0.1  
print(t)
print(t == 5.)

When I run this I get:

>>> ================================ RESTART ================================
>>>  
5.0  
False  

This was a surprise because I expected the comparison to test as True. Then, I tried the following two cases:

''' Case b'''
t = 0
while t<5:
    t = t + 1
print(t)
print(t == 5)

''' Case c'''
t = 0.
while t<5:
    t = t + 0.5
print(t)
print(t == 5)

When I run the last 2 cases (b and c) the comparison in the final statement tests as True. I do not understand why it is so or why it seems that the behavior is not consistent. What am I doing wrong?

+7  A: 

The problem is that binary floating point arithmetic is not precise so you will get small errors in the calculations. In particular the number 0.1 has no exact binary representation. When you calculate using floating point numbers the very small errors cause the result to be slightly incorrect from what you might expect and that makes the equality test fail.

This small error might not be visible when printing the float with the default string representation. Try using repr instead, as this gives a slightly more accurate representation of the number (but still not 100% accurate):

>>> print(repr(t))
4.999999999999998
>>> print(t == 5.)
False

To get an accurate string representation of a float you can use the format method:

>>> print '{0:.60f}'.format(t)
4.999999999999998223643160599749535322189331054687500000000000
>>> print '{0:.60f}'.format(0.1)
0.100000000000000005551115123125782702118158340454101562500000

A general rule with floating point arithmetic is to never make equality comparisons.

The reason why it works when you used 0.5 is because 0.5 does have an exact representation as a binary floating point number so you don't see any problem in that case. Similarly it would work for 0.25 or 0.125.

If you need precise calculations you can use a decimal type instead.

from decimal import Decimal    
step = Decimal('0.1')

t = Decimal(0)
while t < Decimal(5):  
    t += step 

print(t)
print(t == Decimal(5))

Result:

5.0
True
Mark Byers
I would note that the important issue at work here is *representation* error, not *rounding* error (though rounding also occurs); you hint at this in mentioning that it would work if the increment were `0.25`.
Stephen Canon
+4  A: 

NEVER try to test floats for equality.

Floats are often not exactly what you inputted them to be.

In [43]: .1
Out[43]: 0.10000000000000001

So it's much safer to only test floats with inequalities. If you need to test if two floats are nearly equal, use a utility function like near:

def near(a,b,rtol=1e-5,atol=1e-8):
    try:
        return abs(a-b)<(atol+rtol*abs(b))
    except TypeError:
        return False

The rtol parameter allows you to specify relative tolerance. (abs(a-b)/abs(b)) < rtol

The atol parameter allows you to specify absolute tolerance. abs(a-b) < atol

So for example, you could write

t = 0.  
while t<4.9:  
    t = t + 0.1  
    if near(t,1.):
        print('Hiya')
unutbu
+1: Please emphasize **NEVER** in your answer.
S.Lott
Just to make this clear, NEVER test floats for equality in any language. I have seen if float a >= float b .. if float a < float b fail as well. IEEE math also allows a + b = a if a is much larger than b.
Michael Shopsin
Never say never. That said, non-experts *typically* don't want to test floats for equality.
Stephen Canon
@Stephen Canon: if you don't start out with a clear, absolute, non-negotiable position, you have to answer questions like this over and over again. **always** say never.
S.Lott
@S.Lott: The fact is that there isn't an absolute non-negotiable rule in this case. Catchy rules like "never do x" are not a substitute for actual understanding.
Stephen Canon
@Stephen Canon: Not True. They lead to understanding. When someone penetrates the fog of the rule, they can challenge the never. In my experience (only 30 years of teaching programming) I've found that absolutism causes thinking. Starting out with a complex and waffling rule leads to questions like this one. Your experience may vary, but I always say "never" in order for force people to find the exceptions on their own. (Note, BTW, that my comments are all absolutist. Detect a pattern?)
S.Lott