[Tutor] Floating Point Craziness

Steven D'Aprano steve at pearwood.info
Sun Jun 12 19:13:05 CEST 2011


Ryan Strunk wrote:
> Hi everyone,
> I'm designing a timeline. When the user presses the right arrow, 0.1 is
> added to the current position. The user can add events to the timeline, and
> can later scroll back across those events to see what they are. But
> something I absolutely don't understand is happening:
> I used the program to place events at 1.1, 2.1, and 3.1. Here is the end of
> the debug output for arrowing to 3.1 and placing the event:
> position = 2.7
> position = 2.8
> position = 2.9
> position = 3.0
> position = 3.1
> event placed.
> Everything appears straight forward. 


It only *appears* straight forward, it actually isn't. That's because 
the printed output is rounded so as to look nice. Compare:

 >>> x = 1/10.0
 >>> print(x)  # displayed as nicely as possible
0.1
 >>> print('%.25f' % x)  # display 25 decimal places
0.1000000000000000055511151


For speed and simplicity, floats are stored by the computer using 
fractional powers of two. That's fine for numbers which are powers of 
two (1/2, 1/4, 1/256, and sums of such) but numbers that humans like to 
use tend to be powers of 10, not 2.

Unfortunately, many common fractions cannot be written exactly in 
binary. You're probably familiar with the fact that fractions like 1/3 
cannot be written exactly in decimal:

1/3 = 0.33333333... goes on forever

The same is true for some numbers in binary:

1/10 in decimal = 1/16 + 1/32 + 1/256 + ...

also goes on forever. Written in fractional bits:

1/10 decimal = 0.00011001100110011... in binary

Since floats can only store a finite number of bits, 1/10 cannot be 
stored exactly. It turns out that the number stored is a tiny bit larger 
than 1/10:

 >>> Fraction.from_float(0.1) - Fraction(1)/10
Fraction(1, 180143985094819840)

Rounding doesn't help: 0.1 is already rounded as close to 1/10 as it can 
possibly get.

Now, when you print the dictionary containing those floats, Python 
displays (almost) the full precision:

 >>> print {1: 0.1}
{1: 0.10000000000000001}


In newer versions of Python, it may do a nicer job of printing the float 
so that the true value is hidden. E.g. in Python 3.1 I see:


 >>> print({1: 0.1})
{1: 0.1}

but don't be fooled: Python's print display is pulling the wool over 
your eyes, the actual value is closer to 0.10000000000000001.


One consequence of that is that adding 0.1 ten times does not give 1 
exactly, but slightly less than 1:

 >>> x = 0.1
 >>> 1 - sum([x]*10)
1.1102230246251565e-16


(P.S. this is why you should never use floats for currency. All those 
missing and excess fractions of a cent add up to real money.)


To avoid this, you can:


* Upgrade to a newer version of Python that lies to you more often, so 
that you can go on living in blissful ignorance (until such time as you 
run into a more serious problem with floats);

* Use the fraction module, or the decimal module, but they are slower 
than floats; or

* Instead of counting your timeline in increments of 0.1, scale 
everything by 10 so the increment is 1.

That is, instead of:

2.0  2.1  2.2  2.3 ...

you will have

20  21  22  23  24  ...


* Or you just get used to the fact that some numbers are not exact in 
floating point.




-- 
Steven



More information about the Tutor mailing list