Two totals of the same floating-point column, one accumulated strictly in order and one block-by-block, disagree — why?
answer
- rounding after every addition
- commutative but not associative
- big total absorbs small addends
- exact representations are immune
- compare with a relative tolerance
basics
~20 sFloating-point addition rounds at every step, so it is not associative: regrouping the additions changes which roundings happen. A block-at-a-time total keeps partial sums of similar magnitude and usually lands closer to the exact answer than a long sequential one.
solid answer
~50 sA floating-point value holds a fixed number of significant digits, and every addition rounds its result back to a representable value. Addition is therefore commutative but **not associative**: how the additions are grouped decides which roundings occur, so a strictly sequential accumulation and a block-at-a-time one are genuinely different computations of the same sum. The sequential one is not the reference answer — over many values of similar size its running total grows large and each new addend, being small relative to it, is partly or wholly absorbed. Blocked accumulation keeps partial sums nearer in magnitude to the values being added, so it usually lands closer to the exactly rounded total. Practical consequences: the rewrite cannot be compared with an equality, only against a tolerance scaled to the magnitude; and if the numbers must be exact and reproducible, hold them in an exact representation, where the order stops mattering.
go deeper
Recall that fractional values are stored approximately with a fixed number of significant digits, so two ways of adding the same numbers can differ in the last places.
Explain the mechanism: each addition rounds to a representable value, so the grouping decides which roundings occur, and a running total much larger than the next addend absorbs it.
Expect the difference before anyone reports it, know that block size and thread count move it, and compare a rewrite against the old code with a tolerance rather than an equality.
Decide where the pipeline needs an exact representation, such as amounts held in their smallest whole unit, against where a tolerance is acceptable — and write the tolerance down rather than leaving every reviewer to invent one.
## Why addition rounds, and what that breaks A floating-point value stores a fixed number of significant digits and a scale. Adding two of them lines the scales up, adds, and then **rounds the result back** to the nearest representable value, because the exact answer usually needs more digits than the representation has. That single rounding per operation is enough to break associativity. Concretely: once a running total has grown large, an addend smaller than one unit in the last place of that total changes nothing at all — the exact sum rounds straight back to the total you already had. Add a million such small values one at a time to a large running total and you can lose most of them. Add the small values to each other first, and their combined weight is large enough to survive being added to the big one. Addition is still **commutative**: swapping the two operands of a single addition gives the same answer. It is the regrouping that changes things, and candidates who reach for commutativity here are answering a different question. ## The two orders, and which is more accurate | accumulation | what the partial sums look like | typical error growth | |---|---|---| | strictly sequential, left to right | one total that keeps growing, added to small values | grows roughly with the number of values | | blocked or pairwise | many partial sums of similar magnitude, combined at the end | grows far more slowly | | compensated — a second value tracks what each rounding discarded | one total plus a correction term | very small, at some extra cost per value | The point worth carrying out of that table is that **the loop's answer is not the truth**. The reference is the exactly rounded total of the true values, and a blocked pass is usually nearer to it than a long sequential one. Treating the original loop as ground truth and "fixing" the rewrite to reproduce it locks in the worse answer. ## What changes the grouping in practice The grouping is not something you chose once in the source text. It moves when: - the block size of a deliberate loop over blocks changes; - a pass is spread across a different number of threads, so the partial sums are cut differently; - an expression is fused into a single pass rather than materialised step by step; - a tool switches internally between a straight accumulation and a pairwise one according to length. So the answer is deterministic for a fixed configuration and can move when any of that configuration moves. That is worth knowing before someone reports it as flakiness. ## What is affected and what is not - **Floating-point values:** affected. Two groupings differ in the low digits, and the difference grows with the number of values and with the spread of their magnitudes. - **Integers within the representable range:** not affected. The additions are exact, nothing rounds, and any grouping gives the same result. - **Fixed-point decimal amounts held in their smallest whole unit:** not affected, for the same reason. This is why monetary amounts are so often held as whole minor units — it makes the accumulation order irrelevant. - **Integers where the accumulator can overflow and the arithmetic traps rather than wrapping:** the order can decide whether the overflow is reached at all, which is a different hazard with the same smell. ## What this means for a rewrite When a per-record loop is replaced by a whole-column or blocked pass, expect the totals to differ in the last digits, and plan for it: 1. Compare with a **relative tolerance** — the difference scaled against the magnitude of the total — rather than an equality. An absolute threshold tuned on thousands will fail on billions. 2. Let the tolerance grow with the number of values accumulated, because the error bound does. 3. Where the number has to be exact and reproducible across configurations, change the representation rather than the tolerance: hold the amounts in an exact type and the whole problem disappears. 4. Where accuracy matters but the values must stay floating point, a compensated accumulation — keeping a second running value for the part each rounding discarded — buys most of the accuracy back for a modest per-value cost. ## The sentence to avoid "The total of a column is the same number however the pass is organised" is true of exact representations and false of floating point. It is a comfortable thing to assume and it is the reason the disagreement gets escalated as a bug. Say which representation you are talking about, and the claim becomes both true and useful.
- Which of the two totals is the right answer?Neither is a reference merely by being sequential. The reference is the exactly rounded total of the true values, and a blocked or pairwise accumulation is usually nearer to it, because its partial sums stay closer in magnitude to the values being added. Treating the old loop's number as ground truth is the common mistake.
- Why does a total of integer amounts not show this?Because integer addition within the representable range is exact, so nothing rounds and regrouping cannot change the result. The same holds for fixed-point decimal amounts kept in their smallest whole unit. It stops holding where the accumulator can overflow and the arithmetic traps, since then the order can decide whether the overflow is reached.
- What tolerance is reasonable when comparing the two totals?One scaled to the size of the numbers rather than an absolute constant: a relative difference of a few units in the last place per accumulation step is expected, so compare the gap against the magnitude of the total times a small factor that grows with the value count. An absolute threshold that suits thousands fails at billions.
saying these in an interview costs you the question
- Says the sequential loop's answer is the correct one by definition.
- Reports the difference as a defect in the whole-column operator.
- Claims floating-point addition is associative because it is commutative.
- Expects bit-for-bit equality between a blocked and a sequential total.
- Assumes the same disagreement appears with integer amounts within range.
- Calls it non-determinism when the same grouping always gives the same answer.