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

_______________________________________________
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
http://mail.python.org/mailman/listinfo/tutor

Reply via email to