Repository navigation
Pairwise Summator addition and subtraction fail on the 65th repeated merge #494
Description
Reproducer
Repeatedly merging a two-value pairwise Summator fails on the 65th merge. This reproduces without importing mir-stat.
Save as pairwise_merge_repro.d and run with mir-algorithm 3.22.4:
/+ dub.sdl:
name "pairwise-merge-repro"
dependency "mir-algorithm" version="3.22.4"
+/
import mir.math.sum : Summator, Summation;
import std.stdio : writefln;
void main()
{
Summator!(double, Summation.pairwise) batch, total;
batch.put(1.0);
batch.put(1.0);
foreach (i; 0 .. 65)
{
writefln("merge %s, previous sum %s", i + 1, total.sum);
total += batch;
assert(total.sum == 2.0 * (i + 1));
}
}For example:
dub run --single pairwise_merge_repro.d --compiler=dmd --build=debugKeep bounds checks enabled. The original reproductions used direct optimized compiler invocations (dmd -O -inline -i and ldc2 -O2 -enable-inlining -i, with dependency import paths); the DUB command above is a convenient way to resolve those dependencies.
Expected and actual behavior
All 65 merges should succeed, with final sum 130. The sums are correct through merge 64, but merge 65 throws core.exception.ArrayIndexError in mir/math/sum.d at line 933: index 64 exceeds the fixed buffer length 64.
The subtraction form also fails on merge 65. Replace total += batch with total -= batch and change the assertion to total.sum == -2.0 * (i + 1); the expected final sum is -130.
Confirmed environments
- mir-algorithm 3.22.4: reproduced the addition failure with optimized Windows DMD 2.113 and Linux LDC 1.43.
- Upstream
sum.dat commit2da212382e434ef86e93f7b028bc2309205ae00e: reproduced both addition and subtraction failures with optimized Windows DMD. This check overlaid that exact upstream module on the installed dependencies; it was not a full upstream checkout test.
Suspected cause
The pairwise accumulator merge paths insert the source partial sums via put, then adjust the destination counter with:
counter -= rhs.index;
counter += rhs.counter;Those adjustments appear to make the counter inconsistent with the partial-sum tree built by put. Repeated two-element sources leave the counter even while the occupied-buffer index keeps growing, eventually reaching the buffer bound. Both addition and subtraction contain this bookkeeping.
A scratch candidate that removes those adjustments passed 10,000 mixed merges with batch lengths 0 through 32, interleaved scalar insertions, exact-sum checks, and source-preservation checks on Windows DMD and Linux LDC. This is preliminary evidence, not a fully validated fix: broader upstream regression and numerical-accuracy testing is still needed.
The defect was found while reviewing mir-stat accumulator merges, but is independently reproducible in mir-algorithm and is separate from the variance arithmetic changes in libmir/mir-stat#192.
AI-Assisted-By: Codex (GPT-6 Astra Medium)
Activity
jmh530 commentedon Oct 21, 2026
This can fail after just two merges, well below the previously demonstrated 65-merge case:
Reproduced locally with DMD. The second merge underflows the partial-buffer index, producing an out-of-bounds index of
18446744073709551615on a 64-bit build.The accumulators are distinct; this does not involve self-merging. The first merge leaves the counter inconsistent with the stored partials, and the second exposes that inconsistency. Increasing the buffer size would not address this underflow.
With the fix in #495, both assertions pass.