Thought Toys · Strategy & computation · Exhibit 82
Ask any computer for 0.1 + 0.2 and it answers 0.30000000000000004. It isn't sloppy and it isn't broken. There is no such number as 0.1 inside it to add in the first place — only the nearest thing it can build out of halves, quarters and eighths.
One tenth, in binary, cut to the machine's precision bits kept bits thrown away what you asked for
—
—
—
A computer stores a number the way you'd write it in binary: as a sum of halves, quarters, eighths, sixteenths. That works perfectly for 0.5, for 0.25, for 0.75. It does not work for 0.1. In base two, one tenth is 0.0001100110011… with 1100 repeating forever — the same way one third is 0.3333… in the base we happen to use. There is no place to stop that lands on the number you meant.
So the machine stops anyway, at whatever precision it has, and keeps the nearest value it can build. That's the top panel: the bright digits are kept, the dim ones are thrown away, and the arrow shows the rounding. Drag the precision slider. The stored value chases 0.1 and never catches it — at 4 bits or at 53, and, as the note below records, not at 1024 either.
The middle panel is the part people find hard to believe. Those evenly spaced ticks are every number the machine can hold near your answer; between them there is nothing at all. Your exact answer — the ring — usually falls between two ticks, so it gets pushed to one. When 0.1 and 0.2 are each pushed, and their sum is pushed again, the result can land on the tick next door to the nearest one to 0.3. One tick apart is all it takes for a comparison to say no.
And the bottom strip is the honest coda. Try the precisions one by one and the failure blinks on and off: at some widths this sum comes out right, at others it doesn't, and it isn't tidily fixed by using more bits. 53 — the width in every browser, phone and spreadsheet you own — happens to be one of the widths where it doesn't.
improve/verify/82-floating-point.js, 44 checks): the model is
an independent toy float built on exact BigInt rationals that never performs a floating-point operation, and
it reproduces this machine's doubles bit for bit — 0.1, 0.2, 0.3, 1/3, 0.01, and 400 random doubles
round-trip unchanged — so what you're dragging is the same object your browser uses, not a story about it.
It confirms the exact decimals shown above, that the gap is exactly one ulp, that the tie is exact, that
exactly 4 of the 100 cent amounts from $0.01 to $1.00 are representable, and that the error never exceeds
half an ulp over 9,000 random values at three widths. The gate corrected me twice while I wrote it:
I had claimed the addition itself was exact — it isn't, the tie-break is what decides this case — and my
first exact-decimal printer refused a subtraction that isn't dyadic, which is the same lesson from the other
end. Four negative controls kill four wrong diagnoses: a base-ten float built from the identical
code stores 0.1 with zero error and fails on 1/3 instead (so the culprit is the base, not floating point);
truncating instead of rounding-to-nearest breaks the hardware cross-check (so that check has teeth);
0.5 + 0.25 and 0.125 + 0.125 are exact at every width tested (so the claim is not “floats are wrong”); and
printing the answer to two places shows a tidy “0.30” while the comparison still fails (so formatting hides
this rather than fixing it — comparing within a tolerance is the fix that works).
Of all 9,801 pairs of cent amounts, 2,106 — 21.5% — give the wrong answer when added
this way. Which is precisely why money is not kept in binary floats.
Also in Strategy & computation: The evolution of trust →
← the cabinet · Thought Toys — a cabinet of explorable explanations. Exhibit 82.