[Tutor] Floating Point Craziness

Kĩnũthia Mũchane kinuthia.muchane at gmail.com
Mon Jun 13 23:19:52 CEST 2011


On 06/12/2011 08:13 PM, Steven D'Aprano wrote:
> 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
Does it? :-)
>
> 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.
>
>
>
>


-- 
Kĩnũthia



More information about the Tutor mailing list