edit Someone on the reddit thread [1] had the same thought.
[0] https://en.wikipedia.org/wiki/Kahan_summation_algorithm
[1] https://old.reddit.com/r/perl/comments/2zneat/how_you_averag...
edit Someone on the reddit thread [1] had the same thought.
[0] https://en.wikipedia.org/wiki/Kahan_summation_algorithm
[1] https://old.reddit.com/r/perl/comments/2zneat/how_you_averag...
>>> data = (1_000_000.1, 1.1, -1_000_000.1, -1.1) * 25_000
>>> sum(data) / len(data) == -2.3283153183228934e-16
>>> sum(sorted(data)) / len(data) == 2.7122441679239272e-11
>>> stable_mean(data) == -2.571225228716577e-13
>>> float(sum(numpy.array(data, dtype=numpy.float128)) / len(data)) == 2.2648549702353195e-19
Note that in python there is a math.fsum function which return a perfectly rounded summation of floats. There are multiple different techniques for that, but they are more complex than Kahan summation.
>>> math.fsum(data) / len(data) == 0.0
Quirks of the underlying data can yield surprising results.
I was playing around with the "well-known" floating point error result of 0.3 - 0.2 - 0.1 = some small number (instead of 0), and found:
>>> sum((0.3, -0.2, -0.1) * 1_000_000) / 3e6
-2.775557561562891e-23
>>> math.fsum((0.3, -0.2, -0.1) * 1_000_000) / 3e6
-9.25185853854297e-18
>>> stable_mean((0.3, -0.2, -0.1) * 1_000_000)
-1.387894586067203e-17
Namely, the "naive" mean yielded the smallest error (due to cancellation lining up just right such that it's capped after two rounds), fsum yielded a nominally larger one, and stable_mean yielded the biggest error.Of course, if you sort the values (such that we're accumulating -0.2 + ... -0.2 + -0.1 + ... + -0.1 + 0.3 + ... + 0.3, then sum performs much worse, stable_mean is a little worse, and fsum performs exactly the same.
>>> sum(sorted((0.3, -0.2, -0.1) * 1_000_000)) / 3e6
-1.042215244884126e-12
>>> math.fsum(sorted((0.3, -0.2, -0.1) * 1_000_000)) / 3e6
-9.25185853854297e-18
>>> stable_mean(sorted((0.3, -0.2, -0.1) * 1_000_000))
-3.0046468865706775e-16
edit: Naturally, if you want accurate decimal calculations, you should consider using decimal.Decimal or fractions.Fraction (albeit at the cost of significant performance). Using numpy.float128 will help, but it's still subject to rounding errors due to imprecise representation.Hence, as far as those functions are concerned, the naive mean yielded the worst error in both tests, but stable_mean coincidentally happens to be the best in the first test. I'm slightly surprised that none of them got the correct result.
>>> 0.3 - 0.2 - 0.1
-2.7755575615628914e-17
>>> (0.3 - 0.2 - 0.1) / 3
-9.25185853854297e-18
But either way, the fundamental problem in this case (as you noted) is actually due to loss of precision from numeric representation.Kahan summation however is always more precise than naive summation.
It has been a while, but if I remember correctly pairwise sum was in practice as good or better as Kahan summation (and both more stable than Walford's) but faster.
But by far the fastest and still very stable solution was to use the legacy 80 bit x87 fpu as accumulator. Which is not surprising as Kahan explicitly added 80 bit precision to x87 for intermediate computations.