Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sum float precision issue #126181

Closed
anton3s opened this issue Oct 30, 2024 · 7 comments
Closed

sum float precision issue #126181

anton3s opened this issue Oct 30, 2024 · 7 comments
Labels
type-bug An unexpected behavior, bug, or error

Comments

@anton3s
Copy link

anton3s commented Oct 30, 2024

Bug report

Bug description:

I am encountering some strange results in Python 3.12.3 when summing up numbers using the sum function.

It seems that there are some precision issues, which I wouldn't expect with these numbers:

>>> 2.5392 + 0.4608 - 3.0
0.0
>>> sum([2.5392,0.4608,-3.0], 0.0)
1.1102230246251565e-16
>>> import numpy
>>> sum([2.5392,0.4608,-3.0], numpy.float64(0.0))
0.0

I tested on the official docker images of Python 3.11 - 3.13 and version 3.13 also seem affected (3.11 works as expected).

$ docker run -it  python:3.11 python -c "print(sum([2.5392,0.4608,-3.0], 0.0))"
0.0
$ docker run -it  python:3.12 python -c "print(sum([2.5392,0.4608,-3.0], 0.0))"
1.1102230246251565e-16
$ docker run -it  python:3.13 python -c "print(sum([2.5392,0.4608,-3.0], 0.0))"
1.1102230246251565e-16

CPython versions tested on:

3.12, 3.13

Operating systems tested on:

Linux, macOS

@anton3s anton3s added the type-bug An unexpected behavior, bug, or error label Oct 30, 2024
@Eclips4
Copy link
Member

Eclips4 commented Oct 30, 2024

Duplicate of #111933

@Eclips4 Eclips4 marked this as a duplicate of #111933 Oct 30, 2024
@Eclips4 Eclips4 closed this as not planned Won't fix, can't repro, duplicate, stale Oct 30, 2024
@skirpichev
Copy link
Member

So, in short:

  1. sum(lst) != reduce(lambda x, y: x+y, lst), i.e. (2.5392 + 0.4608) + (-3.0) in your case
  2. since CPython 3.12, sum() uses improved variant of compensated summation; more accurate (but slow!) version is in the math.fsum()
  3. numpy uses less accurate approach (partial pairwise summation)

Answer 0.0 is not accurate in fact, you can easily do exact calculations for given floats:

>>> from fractions import Fraction as F
>>> lst = [2.5392, 0.4608, -3.0]
>>> lst_F = list(map(F.from_float, lst))
>>> sum(lst_F)  # nonzero!
Fraction(1, 9007199254740992)
>>> sum(lst_F) - F.from_float(sum(lst))  # sum(lst) produces exact answer here
Fraction(0, 1)

@anton3s
Copy link
Author

anton3s commented Oct 30, 2024

Yes, I understand that I shouldn't do equality comparison with floats.

The problem I have is that those three numbers in my example come from another piece of my program that runs an optimization (using scipy.optimize) with the constraint that the sum of those three numbers is <= 0. But when I then check that constraint in Python, it doesn't hold.

The issue with this is that sum([a,b,c]) != a + b + c which breaks any intuition of what sum means mathematically.
I found it unimaginable that this was intended, but given the outcome of the lengthy discussion in issue #126181, I see that it is, so I won't open up that discussion again.

@skirpichev
Copy link
Member

I shouldn't do equality comparison with floats.

That's a bad idea for any computed floating-point values, not just sums.

with the constraint that the sum of those three numbers is <= 0

I would guess, that this constraint is about the sum of three real numbers. Floating-point numbers aren't real numbers from analysis course.

The issue with this is that sum([a,b,c]) != a + b + c which breaks any intuition of what sum means mathematically.

How you intuition live with the fact that (a + b) + c != a + (b + c) in general? ;)

>>> (0.1 + 0.2) + 0.3
0.6000000000000001
>>> 0.1 + (0.2 + 0.3)
0.6
>>> 0.2 + (0.1 + 0.3)
0.6000000000000001

This is not a worst case. It's easy to find floating-point numbers, where all three possible sums (as commutativity holds for addition, only associativity is broken) - are different:

>>> while True:
...     a, b, c = [random.random() for _ in range(3)]
...     ss = [(a + b) + c, a + (b + c), b + (a + c)]
...     if ss[0] != ss[1] and ss[1] != ss[2] and ss[0] != ss[2]:
...         print([a, b, c])
...         print(ss)
...         break
...         
[0.457290196955373, 0.7565955400028612, 0.38914139486258204]
[1.6030271318208165, 1.603027131820816, 1.6030271318208162]

@anton3s
Copy link
Author

anton3s commented Oct 30, 2024

Ok, point taken. I need to revise my intuitions. ;)

@rhettinger
Copy link
Contributor

rhettinger commented Oct 30, 2024

On the plus side (pun intended), your intuitions about commutatively are now likely to be correct.

>>> Counter([len(set(map(sum, permutations([random(), random(), random()]))))
...     for i in range(1000)])
...
Counter({1: 1000})

Formerly, this would have returned something like Counter({1: 751, 2: 225, 3: 24}) showing that 25% of random triplets had different sums depending on the order of their arguments.

@skirpichev
Copy link
Member

showing that 25% of random triplets had different sums depending on the order of their arguments.

And ~60% already for four arguments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

4 participants