Search code examples
pythonmathprecision

How to overcome a precision error in Python when summing a list of floating point numbers?


In Python3, 0.35 * 10 does not show the same result as summing a list of 10 numbers 0.35.

Python 3.8.1 (v3.8.1:1b293b6006, Dec 18 2019, 14:08:53) 
[Clang 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 0.35 * 10
3.5
>>> a = [0.35 for i in range(0, 10)]
>>> sum(a)
3.5000000000000004

Is it possible to overcome this precision error with Python3 alone? By that I mean without using libraries like numpy.


Solution

  • So, one way to do this that is available on older versions of Python is to use math.fsum, if you are working with a list of float objects. It slower than sum, but it is fairly accurate, so, e.g. with python 3.8:

    (py38) jarrivillaga-mbp16-2019:~ jarrivillaga$ python
    Python 3.8.18 | packaged by conda-forge | (default, Dec 23 2023, 17:23:49)
    [Clang 15.0.7 ] on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    >>> a = [0.35 for i in range(0, 10)]
    >>> sum(a)
    3.5000000000000004
    >>> sum(a) == 0.35*10
    False
    

    However,

    >>> import math
    >>> math.fsum(a)
    3.5
    >>> math.fsum(a) == 0.35*10
    True
    

    Please read the caveats in the docs.

    Also, if you can just upgrade to CPython 3.12, they actually improved the built-in sum function:

    (py312) jarrivillaga-mbp16-2019:~ jarrivillaga$ python
    Python 3.12.1 | packaged by conda-forge | (main, Dec 23 2023, 08:05:03) [Clang 16.0.6 ] on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    >>> a = [0.35 for i in range(0, 10)]
    >>> sum(a)
    3.5
    >>> sum(a) == 0.35*10
    True
    

    From what I understand, numpy.sum (sometimes but not always) uses partial pairwise summation.

    In Python 3.12, when you work with floats, sum will use Arnold Neumaier's variant of compensated summation (a la Kahan).

    math.fsum is apparently even more precise, but slow.