This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: float rounding examples in FAQ are outdated
Type: Stage: patch review
Components: Documentation Versions: Python 3.2, Python 3.3
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: mark.dickinson Nosy List: docs@python, loewis, mark.dickinson, python-dev, terry.reedy, zbysz
Priority: normal Keywords: patch

Created on 2012-03-10 09:35 by zbysz, last changed 2022-04-11 14:57 by admin. This issue is now closed.

Messages (18)
msg155300 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2012-03-10 09:35
http://docs.python.org/dev/faq/design.html#why-are-floating-point-calculations-so-inaccurate

This whole paragraph is wrong since #9337 (Make float.__str__ behave identically to float.__repr__).
"""
The str() function prints fewer digits and this often results in the more sensible number that was probably intended:
>>> 1.1 - 0.9
0.20000000000000007
>>> print(1.1 - 0.9)
0.2
"""

Applies from 3.2 onwards.
msg155302 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2012-03-10 11:10
Agreed.  Do you have a suggested rewrite?

That faq entry could be improved significantly in many ways;  the tutorial material is much better.  I'm wondering whether it would be better to cut down the FAQ to much something much briefer, and leave the link to the tutorial for people who want more.
msg155303 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2012-03-10 11:26
Proposed rewrite:




Why are floating point calculations inaccurate?
-----------------------------------------------

Users are often surprised by results like this::

   >>> 1.2 - 1.0
   0.199999999999999996

and think it is a bug in Python.  It's not.  This has little to do with Python,
and much more to do with how the underlying platform handles floating-point
numbers.

Python floats are stored internally in binary floating-point, using a fixed
precision (typically 53 bits).  Many numbers that can be written easily in
decimal notation (``1.2``, for example), cannot be expressed exactly in this
internal binary format.  After::

   >>> x = 1.2

the value stored for x is a (very good) approximation to the value ``1.2``, but
is not exactly equal to it.  (On a typical machine, the actual stored value
is::

   1.1999999999999999555910790149937383830547332763671875

which is accurate to around 16 decimal digits.)  Similarly, the result of any
floating-point operation must often be rounded to fit into the internal format,
resulting in another tiny error.

For a more detailed explanation of what's involved, please see the chapter on
:ref:`floating point arithmetic <tut-fp-issues>` in the Python tutorial.
msg155310 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2012-03-10 13:56
+1
msg155311 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2012-03-10 14:09
On 03/10/2012 12:26 PM, Mark Dickinson wrote:
>
> Mark Dickinson<dickinsm@gmail.com>  added the comment:
>
> Proposed rewrite:

Hi,
thanks for the quick reply. If we were to rewrite the whole entry, some 
more changes could be done:

I think it would be useful to mention explicitly that Python simply uses 
the native floating-point implementation in hardware and thus behaves 
very similarly to other languages which do this, for instance C or Java. 
This should clear up a lot of the behaviour for people who know other 
programming languages. "how the underlying platform handles 
floating-point" says something very similar, but the reader needs to 
understand what the "underlying platform" exactly is.

It is easy to count, that exactly 17 digits are accurate.

I have to admit, that I'm completely lost here --- why would a vastly 
inaccurate number (with more than half of digits wrong) be ever stored?
If "1.2" is converted to a float (a C double in current implementation), 
it has 15.96 decimal digits of precision.

"Similarly, the result of a floating-point operation must be rounded to 
fit into the fixed precision, often resulting in another tiny error." ?
msg155312 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2012-03-10 14:17
[part mangled by the tracker]

">     1.1999999999999999555910790149937383830547332763671875
">
"> which is accurate to around 16 decimal digits.)

It is easy to count, that exactly 17 digits are accurate.

I have to admit, that I'm completely lost here --- why would a vastly
inaccurate number (with more than half of digits wrong) be ever stored?
If "1.2" is converted to a float (a C double in current implementation),
it has 15.96 decimal digits of precision.

" > Similarly, the result of any
" > floating-point operation must often be rounded to fit into the
" > internal format, resulting in another tiny error.
"Similarly, the result of a floating-point operation must be rounded to 
fit into the fixed precision, often resulting in another tiny error." ?
msg155314 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2012-03-10 14:37
> I think it would be useful to mention explicitly that Python simply uses 
> the native floating-point implementation in hardware and thus behaves 
> very similarly to other languages which do this, for instance C or Java. 

Agreed that it's useful to say something here to make it obvious that Python's behaving just like most other mainstream programming languages.  Perhaps most straightforward would be to state that CPython just uses C doubles.  (That sidesteps nitpicking about software floating-point :-).

> It is easy to count, that exactly 17 digits are accurate.

Hmm;  that depends a bit on your definitions.  The relative error here is around 3.7e-17, so in this particular case you're getting something between 16 and 17 decimal digits of accuracy.  Other cases would produce different relative errors, with a max relative error for normal cases of 2**-53, or approximately 1.1e-16.

> I have to admit, that I'm completely lost here --- why would a vastly 
> inaccurate number (with more than half of digits wrong) be ever stored?

Because that number is the unique closest representable IEEE 754 double to the target number (1.2).  You can go through all the steps here:

 - convert 1.2 to binary, to get:

    1.001100110011001100110011.....  repeated ad infinitum

 - round to the nearest 53-bit number:

    1.0011001100110011001100110011001100110011001100110011

 - convert back to decimal to get the number that I gave.

But this sort of detail is already there in the tutorial, and doesn't seem appropriate for the FAQ entry.  For the FAQ, maybe it would be less confusing to give only about a 20-digit approximation.

> "Similarly, the result of any
> floating-point operation must often be rounded to fit into the 
> internal format,
> resulting in another tiny error." ?

The aim here was to make the point that it's not just conversion from decimal to binary that introduces errors;  it's also true that any arithmetic operation has the potential to introduce errors.  For example, 10**16 and 1 are both exactly representable as floats, but the sum of 10**16 + 1 is not;  the result has to be rounded to fit into a float.  Suggestions for rewording to make this clearer are welcome!
msg155315 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2012-03-10 14:50
> I think it would be useful to mention explicitly that Python simply uses
> the native floating-point implementation in hardware and thus behaves
> very similarly to other languages which do this, for instance C or Java.
> This should clear up a lot of the behaviour for people who know other
> programming languages. "how the underlying platform handles
> floating-point" says something very similar, but the reader needs to
> understand what the "underlying platform" exactly is.

Well, people may just be as confused about the term "native 
implementation" as they are seemingly confused about "underlying
platform".

 >
> I have to admit, that I'm completely lost here --- why would a vastly
> inaccurate number (with more than half of digits wrong) be ever stored?
> If "1.2" is converted to a float (a C double in current implementation),
> it has 15.96 decimal digits of precision.

Since it has this number of decimal digits of precision, the text says
"about 16", rather than "exactly 17", which would mislead people into
thinking that they get 17 digits of precision.

I don't understand your confusion. The numbers are stored in binary,
and it's *not* the case that half of the digits are wrong. In binary,
all 53 bits of mantissa are correct. When displaying this number in
decimal (which is not the way in which it is stored), then you many more
decimal digits to make the decimal representation correct.
msg155334 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2012-03-10 18:21
Proposed rewrite (building on Mark's version):
- mention C doubles
- explain ``1.2`` example a bit more

"""
Why are floating-point calculations inaccurate?
-----------------------------------------------

Users are often surprised by results like this::

   >>> 1.2 - 1.0
   0.199999999999999996

and think it is a bug in Python.  It's not.  This has little to do with Python,
and much more to do with how the underlying platform handles floating-point
numbers.

The float type in CPython simply uses C ``double`` for storage. The number is stored in binary floating-point with a fixed precision (typically 53 bits) and Python uses C operations, which in turn rely on the hardware implementation in the processor, to perform floating-point operations. This means that as far as floating-point operations are concerned, Python behaves like many popular languages including C and Java.

Many numbers that can be written easily in decimal notation (``1.2``, for example), cannot be expressed exactly in the binary format.  After::

   >>> x = 1.2

the value stored for ``x`` is a (very good) approximation of the value ``1.2``, but
is not exactly equal to it.  On a typical machine, the actual stored value
is::

   1.0011001100110011001100110011001100110011001100110011 (binary)

which is approximately::

   1.19999999999999995559 (decimal)

53 binary digits are equivalent to about 16 decimal digits and in this case the first 17 digits are correct after the conversion back to decimal.

Similarly, the result of any floating-point operation must be rounded to 
fit into the fixed precision, often resulting in another tiny error.

For a more detailed explanation of what's involved, please see the chapter on
:ref:`floating point arithmetic <tut-fp-issues>` in the Python tutorial.
"""
msg155341 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2012-03-10 18:52
> - explain ``1.2`` example a bit more

+1 for giving the binary representation; -1 for rounding
the decimal version of it.
msg155349 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2012-03-10 20:10
On 03/10/2012 07:52 PM, Martin v. Löwis wrote:
>> - explain ``1.2`` example a bit more
>
> +1 for giving the binary representation; -1 for rounding
> the decimal version of it.

Oh, 1.1999999999999999555910790149937383830547332763671875
is exactly equal to 
1.0011001100110011001100110011001100110011001100110011. In this case 
yes, rounding it is not beneficial.

So one more version (without this rounding):

"""
Why are floating-point calculations inaccurate?
-----------------------------------------------

Users are often surprised by results like this::

    >>> 1.2 - 1.0
    0.199999999999999996

and think it is a bug in Python.  It's not.  This has little to do with 
Python, and much more to do with how the underlying platform handles 
floating-point numbers.

The float type in CPython simply uses C ``double`` for storage. The 
number is stored in binary floating-point with a fixed precision 
(typically 53 bits) and Python uses C operations, which in turn rely on 
the hardware implementation in the processor, to perform floating-point 
operations. This means that as far as floating-point operations are 
concerned, Python behaves like many popular languages including C and Java.

Many numbers that can be written easily in decimal notation (``1.2``, 
for example), cannot be expressed exactly in the binary format.  After::

    >>> x = 1.2

the value stored for ``x`` is a (very good) approximation of the value 
``1.2``, but is not exactly equal to it.  On a typical machine, the 
actual stored value is::

    1.0011001100110011001100110011001100110011001100110011 (binary)

which is exactly::

    1.1999999999999999555910790149937383830547332763671875 (decimal)

53 binary digits are equivalent to about 16 decimal digits and in this 
case the first 17 digits are correct after the conversion back to decimal.

Similarly, the result of any floating-point operation must be rounded to 
fit into the fixed precision, often resulting in another tiny error.

For a more detailed explanation of what's involved, please see the 
chapter on :ref:`floating point arithmetic <tut-fp-issues>` in the 
Python tutorial.
"""
msg156068 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2012-03-16 18:57
The last version in the message above looks good to me. Ready to markup and apply?
msg156081 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2012-03-16 20:37
Fine by me.

Language nitpick:  "approximation to" sounds a bit better to my ears than "approximation of"
msg156160 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2012-03-17 14:26
Both appear to be commonly used. Both sound OK to me.
msg160328 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2012-05-10 07:31
Zbyszek, have you signed a contributor agreement form? [1]

If not, please could you do so?  Then I can apply this doc contribution.

Thanks!


[1] http://www.python.org/psf/contrib/
msg160332 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2012-05-10 10:11
Done now.

Thanks,
Zbyszek
msg160564 - (view) Author: Roundup Robot (python-dev) (Python triager) Date: 2012-05-13 20:04
New changeset 2b2a7861255d by Mark Dickinson in branch '3.2':
Issue #14245: Improve floating-point entry in FAQ.  Thanks Zbyszek Jędrzejewski-Szmek for some of the wording.
http://hg.python.org/cpython/rev/2b2a7861255d

New changeset a79b07e05d0d by Mark Dickinson in branch 'default':
Issue #14245: Merge changes from 3.2.
http://hg.python.org/cpython/rev/a79b07e05d0d
msg160566 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2012-05-13 20:10
Thanks for all the feedback.  I made another round of minor edits to the latest version and committed the result.
History
Date User Action Args
2022-04-11 14:57:27adminsetgithub: 58453
2012-05-13 20:10:43mark.dickinsonsetstatus: open -> closed
resolution: fixed
messages: + msg160566
2012-05-13 20:04:26python-devsetnosy: + python-dev
messages: + msg160564
2012-05-10 10:11:46zbyszsetmessages: + msg160332
2012-05-10 07:31:24mark.dickinsonsetmessages: + msg160328
2012-05-09 07:24:48mark.dickinsonsetassignee: docs@python -> mark.dickinson
2012-03-17 14:26:22zbyszsetmessages: + msg156160
2012-03-16 20:37:23mark.dickinsonsetmessages: + msg156081
2012-03-16 18:57:37terry.reedysetnosy: + terry.reedy
messages: + msg156068

keywords: + patch
stage: patch review
2012-03-10 20:10:11zbyszsetmessages: + msg155349
2012-03-10 18:52:17loewissetmessages: + msg155341
2012-03-10 18:21:25zbyszsetmessages: + msg155334
2012-03-10 14:50:59loewissetmessages: + msg155315
2012-03-10 14:37:34mark.dickinsonsetmessages: + msg155314
2012-03-10 14:17:01zbyszsetmessages: + msg155312
2012-03-10 14:09:47zbyszsetmessages: + msg155311
2012-03-10 13:56:03loewissetnosy: + loewis
messages: + msg155310
2012-03-10 11:26:27mark.dickinsonsetmessages: + msg155303
2012-03-10 11:10:49mark.dickinsonsetnosy: + mark.dickinson
messages: + msg155302
2012-03-10 09:35:12zbyszcreate