Why isn't Python king of the hill?

Tim Peters tim.one at home.com
Sun Jun 3 21:23:00 EDT 2001

[Nick Perkins]
> >>> from math import *
> >>> sqrt(2)**2
> 2.0000000000000004
> >>> sin(pi)
> 1.2246063538223773e-016
> Neither of these 'errors' would be eliminated by using Rationals.
> I am sure there are many similar examples.
> note that:
> >>> cos(pi)
> -1.0
> actually works, for some reason (luck?)

Floats aren't random -- so luck isn't *really* an issue.  What is an issue
is that the mathematical PI isn't exactly representable as a float (and
binary vs decimal has nothing to do with *this* one), so the machine pi is
really some other number,

    pi = PI + tiny

where "tiny" is an error in the last bit (or so) introduced by finite
precision.  Using angle sum formulas,

    sin(pi) =
    sin(PI+tiny) = cos(tiny)*sin(PI) + sin(tiny)*cos(PI) =
                   cos(tiny)*0       + sin(tiny)*(-1) =
                                       -sin(tiny) =
    -tiny - tiny**3/6 - ...

tiny**3/6 is too small to have any effect when added to tiny (finite
precision again), so the result you see is plain old -tiny.  If you got 0
instead, it would be wrong!  If you carry on the example,

>>> math.sin(math.pi)
>>> math.log(_)/math.log(2)

you can see the result is about 2**-52, right where "it should be" for a
low-bit error in PI given that floats have 53 bits of precision.

For the other one,

    cos(pi) =
    cos(PI+tiny) = cos(PI)*cos(tiny) - sin(PI)*sin(tiny) =
                      (-1)*cos(tiny) -       0*sin(tiny) =
                          -cos(tiny) =
    -1 - tiny**2/2 - ...

Since tiny is about 2**-52, tiny**2/2 is about 2**-105, and that's too small
to have any effect when added to the 53-bit value of -1.0.  So -1.0 on the
nose is what you get.

Of course sin() and cos() aren't actually *implemented* like that, but a
careful implementation will deliver the same result in the end.

More information about the Python-list mailing list