Skip to content

Commit 8bd1d4f

Browse files
committed
Update for the new float.__repr__()
1 parent d258d1e commit 8bd1d4f

File tree

1 file changed

+64
-39
lines changed

1 file changed

+64
-39
lines changed

Doc/tutorial/floatingpoint.rst

Lines changed: 64 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -48,32 +48,43 @@ decimal value 0.1 cannot be represented exactly as a base 2 fraction. In base
4848

4949
0.0001100110011001100110011001100110011001100110011...
5050

51-
Stop at any finite number of bits, and you get an approximation. This is why
52-
you see things like::
51+
Stop at any finite number of bits, and you get an approximation. On most
52+
machines today, floats are approximated using a binary fraction with
53+
the numerator using the first 53 bits following the most significant bit and
54+
with the denominator as a power of two. In the case of 1/10, the binary fraction
55+
is ``3602879701896397 / 2 ** 55`` which is close to but not exactly
56+
equal to the true value of 1/10.
57+
58+
Many users are not aware of the approximation because of the way values are
59+
displayed. Python only prints a decimal approximation to the true decimal
60+
value of the binary approximation stored by the machine. On most machines, if
61+
Python were to print the true decimal value of the binary approximation stored
62+
for 0.1, it would have to display ::
5363

5464
>>> 0.1
55-
0.10000000000000001
65+
0.1000000000000000055511151231257827021181583404541015625
5666

57-
On most machines today, that is what you'll see if you enter 0.1 at a Python
58-
prompt. You may not, though, because the number of bits used by the hardware to
59-
store floating-point values can vary across machines, and Python only prints a
60-
decimal approximation to the true decimal value of the binary approximation
61-
stored by the machine. On most machines, if Python were to print the true
62-
decimal value of the binary approximation stored for 0.1, it would have to
63-
display ::
67+
That is more digits than most people find useful, so Python keeps the number
68+
of digits manageable by displaying a rounded value instead ::
6469

65-
>>> 0.1
66-
0.1000000000000000055511151231257827021181583404541015625
70+
>>> 1 / 10
71+
0.1
6772

68-
instead! The Python prompt uses the built-in :func:`repr` function to obtain a
69-
string version of everything it displays. For floats, ``repr(float)`` rounds
70-
the true decimal value to 17 significant digits, giving ::
73+
Just remember, even though the printed result looks like the exact value
74+
of 1/10, the actual stored value is the nearest representable binary fraction.
7175

72-
0.10000000000000001
76+
Interestingly, there are many different decimal numbers that share the same
77+
nearest approximate binary fraction. For example, the numbers ``0.1`` and
78+
``0.10000000000000001`` and
79+
``0.1000000000000000055511151231257827021181583404541015625`` are all
80+
approximated by ``3602879701896397 / 2 ** 55``. Since all of these decimal
81+
values share the same approximation, any one of them could be displayed and
82+
while still preserving the invariant ``eval(repr(x)) == x``.
7383

74-
``repr(float)`` produces 17 significant digits because it turns out that's
75-
enough (on most machines) so that ``eval(repr(x)) == x`` exactly for all finite
76-
floats *x*, but rounding to 16 digits is not enough to make that true.
84+
Historically, the Python prompt and built-in :func:`repr` function would chose
85+
the one with 17 significant digits, ``0.10000000000000001``, Starting with
86+
Python 3.1, Python (on most systems) is now able to choose the shortest of
87+
these and simply display ``0.1``.
7788

7889
Note that this is in the very nature of binary floating-point: this is not a bug
7990
in Python, and it is not a bug in your code either. You'll see the same kind of
@@ -85,23 +96,28 @@ Python's built-in :func:`str` function produces only 12 significant digits, and
8596
you may wish to use that instead. It's unusual for ``eval(str(x))`` to
8697
reproduce *x*, but the output may be more pleasant to look at::
8798

88-
>>> print(str(0.1))
89-
0.1
99+
>>> str(math.pi)
100+
'3.14159265359'
101+
102+
>>> repr(math.pi)
103+
'3.141592653589793'
104+
105+
>>> format(math.pi, '.2f')
106+
'3.14'
90107

91-
It's important to realize that this is, in a real sense, an illusion: the value
92-
in the machine is not exactly 1/10, you're simply rounding the *display* of the
93-
true machine value.
108+
It's important to realize that this is, in a real sense, an illusion: you're
109+
simply rounding the *display* of the true machine value.
94110

95111
Other surprises follow from this one. For example, after seeing ::
96112

97-
>>> 0.1
98-
0.10000000000000001
113+
>>> format(0.1, '.17g')
114+
'0.10000000000000001'
99115

100116
you may be tempted to use the :func:`round` function to chop it back to the
101117
single digit you expect. But that makes no difference::
102118

103-
>>> round(0.1, 1)
104-
0.10000000000000001
119+
>>> format(round(0.1, 1), '.17g')
120+
'0.10000000000000001'
105121

106122
The problem is that the binary floating-point value stored for "0.1" was already
107123
the best possible binary approximation to 1/10, so trying to round it again
@@ -115,7 +131,7 @@ Another consequence is that since 0.1 is not exactly 1/10, summing ten values of
115131
... sum += 0.1
116132
...
117133
>>> sum
118-
0.99999999999999989
134+
0.9999999999999999
119135

120136
Binary floating-point arithmetic holds many surprises like this. The problem
121137
with "0.1" is explained in precise detail below, in the "Representation Error"
@@ -191,10 +207,7 @@ floating-point representation is assumed.
191207
:dfn:`Representation error` refers to the fact that some (most, actually)
192208
decimal fractions cannot be represented exactly as binary (base 2) fractions.
193209
This is the chief reason why Python (or Perl, C, C++, Java, Fortran, and many
194-
others) often won't display the exact decimal number you expect::
195-
196-
>>> 0.1
197-
0.10000000000000001
210+
others) often won't display the exact decimal number you expect.
198211

199212
Why is that? 1/10 is not exactly representable as a binary fraction. Almost all
200213
machines today (November 2000) use IEEE-754 floating point arithmetic, and
@@ -237,26 +250,38 @@ that over 2\*\*56, or ::
237250

238251
7205759403792794 / 72057594037927936
239252

253+
Dividing both the numerator and denominator by two reduces the fraction to::
254+
255+
3602879701896397 / 36028797018963968
256+
240257
Note that since we rounded up, this is actually a little bit larger than 1/10;
241258
if we had not rounded up, the quotient would have been a little bit smaller than
242259
1/10. But in no case can it be *exactly* 1/10!
243260

244261
So the computer never "sees" 1/10: what it sees is the exact fraction given
245262
above, the best 754 double approximation it can get::
246263

247-
>>> .1 * 2**56
248-
7205759403792794.0
264+
>>> 0.1 * 2 ** 55
265+
3602879701896397.0
249266

250-
If we multiply that fraction by 10\*\*30, we can see the (truncated) value of
251-
its 30 most significant decimal digits::
267+
If we multiply that fraction by 10\*\*60, we can see the value of out to
268+
60 decimal digits::
252269

253-
>>> 7205759403792794 * 10**30 / 2**56
254-
100000000000000005551115123125
270+
>>> 3602879701896397 * 10 ** 60 // 2 ** 55
271+
1000000000000000055511151231257827021181583404541015625
255272

256273
meaning that the exact number stored in the computer is approximately equal to
257274
the decimal value 0.100000000000000005551115123125. Rounding that to 17
258275
significant digits gives the 0.10000000000000001 that Python displays (well,
259276
will display on any 754-conforming platform that does best-possible input and
260277
output conversions in its C library --- yours may not!).
261278

279+
The :mod:`fractions` and :mod:`decimal` modules make these calculations
280+
easy::
262281

282+
>>> from decimal import Decimal
283+
>>> from fractions import Fraction
284+
>>> print(Fraction.from_float(0.1))
285+
3602879701896397/36028797018963968
286+
>>> print(Decimal.from_float(0.1))
287+
0.1000000000000000055511151231257827021181583404541015625

0 commit comments

Comments
 (0)