Coherence Energy Labs

Float, made exact

Add the same numbers in a different order and floating point hands you a different answer. Everyone who has shipped numerical code knows this, and there is a well-known cure: compensated summation, Kahan's algorithm, the one in every numerical methods course. Below, your own browser runs Kahan's algorithm against the same sum in five hundred different orders, and watches it disagree with itself. Then it runs the method underneath our compiler, which does not.

What you are watching. Nothing about the numbers changes when you shuffle. Addition is commutative in arithmetic, so every ordering has the same sum. Four of these five methods disagree with themselves anyway, because each intermediate result is squeezed back into 53 bits before the next term arrives, and which digits fall off the end depends on the order they arrived in. Compensation narrows the error. It does not remove the dependence.

The scoreboard

Every case above, put through every method, in two hundred random orders each, computed on your machine when this page loaded. A method counts as wrong if it missed the correct answer in any one of those orders. Scoring it as wrong only when it never once got there would reward stumbling onto the right answer by luck in some particular order, which is not a property you can build anything on.

MethodWhat it isGave more than one answerWrong on some ordering
Read the third row twice. Kahan compensated summation is the answer taught in numerical analysis courses and shipped in production code everywhere. It is genuinely more accurate than adding left to right. It is still order-dependent, and on these cases it never lands on the correct answer. Neumaier's improved variant does better still, holds one answer across every ordering, and then misses the correctly rounded result on the one case with no adversarial structure in it at all: an ordinary dot product of random numbers. Being close is not the same property as being right.

Try to break it

Cases we chose prove nothing on their own; we chose them. So generate your own. This draws random vectors with exponents spread across the whole double range, sums each one in several random orders, and records every method that disagreed with itself. It runs until you stop it.

random dot products tried0
naive disagreed with itself0
pairwise disagreed0
Kahan disagreed0
Neumaier disagreed0
exact accumulation disagreed0

Each trial sums one random dot product in six different orders and compares the results bit for bit.

The same answer as Python, on your machine, bit for bit

Reproducibility across one browser is not worth much. These vectors were computed by the reference implementation in Python and published as raw IEEE-754 bit patterns, so nothing is lost to decimal formatting. Your browser just recomputed every one of them with the JavaScript twin. The two implementations share no code.

CHECKING 0 of 0 vectors reproduced in 0 ms
CaseTermsPython saidYour browser said

How it works

Every IEEE-754 double is exactly an integer times a power of two. That is not an approximation, it is what the format is. So the true value of a dot product is an exact sum of integer products, and integers do not round. Line every product up against the smallest exponent present, add them in a wide integer where nothing is ever discarded, and round the total once at the end.

The result is the infinitely-precise value rounded a single time, which is the uniquely correct double. Because it is unique, there is nothing left for summation order, fused multiply-add, extended precision, or parallel scheduling to change. The usual approach of pinning the order is weaker: it gives you the same answer every time, but a specific wrong one, and a different pinned order gives a different wrong one.

Where this stops, honestly. Sums and products are fixed by exact accumulation. Transcendental functions are not. No two platforms agree on the last bit of a logarithm or a sine, and unlike a sum there is no accumulation trick that makes them agree, so we hold that lane closed rather than ship an answer that changes between machines. Float reductions on a GPU are excluded for the same reason and routed instead through a proven worst-case error bound. Where a computation genuinely needs a transcendental and needs to be reproducible, it goes to integer fixed point, which is what the re-executable figure does for its logarithm.

Where this code comes from

The file doing the arithmetic is floatexact.js, next to this page. It is the compiler's own fabric/polyglot/floatexact.mjs, byte for byte, with only the export keywords and its command-line block removed so it loads as a classic script, because browsers refuse to import modules from a saved local file and this page has to keep working after you save it. That equivalence is not a promise: the build runs both files over four thousand random dot products and requires identical bits, and it fails if either one drifts.

No network, no GPU. Save this folder and it keeps working offline.