Skip to main content

DuckDB fsum returns NaN instead of the sum when the running sum overflows

· 3 min read

In DuckDB 2.0.0, fsum returns NaN instead of the sum when the running sum overflows.

Reproduce it​

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

DuckDB 2.0.0 returns:

-nan

The correct answer​

The correct answer is 1e+308. 1e308 + 1e308 − 1e308 = 1e308.

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([1e308, 1e308, -1e308])) # 1e+308

Why DuckDB gets it wrong​

DuckDB's fsum keeps its Kahan running sum in a double. The sum overflows to infinity even though the true sum fits in a double.

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
1e3081e3081e3080First value into the sum
1e3081e308infinfThe sum passes the largest double, so the sum and the correction become inf
−1e308−infnannan−1e308 − inf = −inf, and inf + −inf = nan
merge: nannannannanMerging the sum into an empty one carries the nan
merge: nannannannanMerging minus the correction keeps the nan

Result: fsum returns the sum, nan, which DuckDB prints as -nan.

How to fix it​

A fixed-point sum keeps the sum exact with room for sums far beyond the largest double. It does this 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
1e3081e308 × 2^10741e308 × 2^1074First value into the sum, exactly
1e3081e308 × 2^10742 × 1e308 × 2^1074Added exactly, with no overflow
−1e308−1e308 × 2^10741e308 × 2^1074Added exactly

Result: 1e308 × 2^1074 × 2^−1074 = 1e+308.

Vendor issue​

Reported to DuckDB as duckdb/duckdb#28164.

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