Unexpected results comparing float to Fraction

Steven D'Aprano steve+comp.lang.python at pearwood.info
Mon Jul 29 16:19:58 EDT 2013


On Mon, 29 Jul 2013 18:04:19 +0100, MRAB wrote:

> I thought that you're not meant to check for equality when using floats.

Heavens no. You're only meant to *mostly* not check for equality using 
floats. As Miracle Max might say:

"It just so happens that floats are only MOSTLY inexact. There's a big 
difference between mostly inexact and all inexact. Mostly inexact is 
slightly exact."


Or to be a little more serious, "never check floats for equality" is 
actually pretty lousy advice. As Professor William Kahan puts it, the 
word "never" makes it rank superstition. 

There are three main problems with the general advice "never check floats 
for equality":

1) there are perfectly fine situations where you can check floats 
   for (in)equality without any problem, e.g.:

   if x == 0.0: print "Division by zero"
   else: y = 1/x  # perfectly safe

2) most of the time, those giving this advice don't actually say 
   what you should do instead;

3) and when they do, it's often either flat-out wrong, or at least
   incomplete.


For example, you might have been told to instead check whether your float 
is less than some epsilon:

abs(x - y) < eps

But chances are you've never been given any sort of reliable, 
deterministic algorithm for deciding what value eps should have. (Mostly 
because there is no such algorithm!) Or for deciding when to use absolute 
differences, like the above, and when to use relative differences.

Another issue: if eps is too big, you're taking distinct values and 
treating them as the same, which is bad. And if eps is too small, you're 
actually doing what you said you should never do, only slower.

E.g. suppose you want to check for a float which equals some value M, 
I'll pick M = 2199023255552.0 semi-arbitrarily, but it could be any of 
many numbers. You don't want to use equality, so you pick an epsilon:

eps = 1e-6  # seems reasonable...

and then test values like this:

M = 2199023255552.0
if abs(x - M) <= eps:
    print("close enough")


That is just a longer and slower way of calculating x == M. Why? Because 
the two floats immediately adjacent to M differ by more than your epsilon:

py> M = 2199023255552.0
py> M.hex()
'0x1.0000000000000p+41'
py> float.fromhex('0x1.0000000000001p+41') - M
0.00048828125
py> M - float.fromhex('0x1.fffffffffffffp+40')
0.000244140625

So in this case, any epsilon below 0.000244140625 is just wasting your 
time, since it cannot possibly detect any value other than M itself. And 
that critical cut-off depends on the numbers you are calculating with.

Oh, for what it's worth, I don't pretend to know how to choose an epsilon 
either. I can sometimes recognise a bad epsilon, but not a good one. 
Floats are *hard*.


-- 
Steven



More information about the Python-list mailing list