Skip to main content

DuckDB fsum returns the wrong sum when small values are summed before the large values cancel

· 3 min read

In DuckDB 2.0.0, fsum returns the wrong sum when small values are summed before the large values cancel.

Reproduce it​

SELECT fsum(v) FROM (VALUES (1e16::DOUBLE), (1.0), (-1e16)) t(v);

DuckDB 2.0.0 returns:

0.0

The correct answer​

The correct answer is 1.0. 1e16 + 1 − 1e16 = 1. The Python code determines the exact sum with fractions and rounds it once to a double:

from fractions import Fraction as F
def fsum(values):
# the exact sum, rounded once to a double
return float(sum(F(v) for v in values))
print(fsum([1e16, 1.0, -1e16])) # 1.0

Why DuckDB gets it wrong​

DuckDB's fsum applies its Kahan correction by folding it into the next value it adds. When that next value is large, the correction is rounded away.

Kahan Algorithm​

  1. adjusted value = value − correction
  2. new sum = old sum + adjusted value
  3. correction = (new sum − old sum) − adjusted value
  4. merge: add the sum, then minus the correction, into an empty sum with steps 1–3
  5. result = sum
ValueAdjustedSumCorrectionExplanation
——00init
1e161e161e160First value into the sum
111e16−1The 1 is lost in the sum but caught as a −1 correction
−1e16−1e1600−1e16 − (−1) rounds to −1e16, so the correction is lost
merge: 0000The partial state is merged into an empty one: the sum
merge: −0−000Then minus the correction

Result: fsum returns the sum, 0.0.

How to fix it​

A fixed-point sum keeps the sum exact by adding every value as a whole number of units of the smallest double.

Fixed-Point Algorithm​

  1. Start with a sum of 0
  2. Turn each value into a whole number of units of the smallest double (value × 2^1074)
  3. Add that whole number to the sum exactly

Final Step: result = sum × 2^−1074, rounded once

ValueUnitsSum (units)Explanation
——0init
1e161e16 × 2^10741e16 × 2^1074First value into the sum, exactly
11 × 2^1074(1e16 + 1) × 2^1074The 1 is kept: whole numbers add exactly
−1e16−1e16 × 2^10741 × 2^10741e16 cancels exactly

Result: 1 × 2^1074 × 2^−1074 = 1.0.

Vendor issue​

Reported to DuckDB as duckdb/duckdb#28161.

writePath​

FSUM function​

writePath's FSUM uses an exact fixed-point sum. No precision is lost because every value is added as a whole number. The sum is rounded once at the end, so the result is the most precise double. The result does not depend on row order.

About us​

writePath is an AWS Marketplace SaaS for sub-millisecond aggregations over the first or last N rows of your data with no pre-computation. It offers 65 high-precision aggregation functions tested against reference implementations.

www.thewritepath.net