Thought Toys · Strategy & computation · Exhibit 82

Your computer can't hold one tenth

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

Try a sum
your turn — drag the precision and watch the error shrink forever without reaching zero

What you're seeing

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.

The rule, exactly. A binary float holds x = m·2e with the significand m an integer of a fixed width p (53 bits in the IEEE-754 double that runs this page). The storable values near x are therefore spaced exactly ulp(x) = 2e — one “unit in the last place” apart, and storing means rounding to the nearest of them, ties to the even significand. A value is held exactly if and only if its reduced denominator is a power of two — so 1/4 yes, 1/10 never, at any width. The error is then at most half an ulp, which is the best any rounding can promise. For the famous case: 0.1 rounds up, 0.2 rounds up, and their exact sum lands precisely on the midpoint between the two floats either side — a perfect tie, broken upward to the even significand 5404319552844596, one ulp (2−54 ≈ 5.55×10−17) above the nearest float to 0.3. Verified in node (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 →

All 25 in Strategy & computation
  1. 10The evolution of trust
  2. 23Sorting algorithms
  3. 24PageRank & the random surfer
  4. 25Huffman coding
  5. 26Dijkstra's shortest path
  6. 27Nash equilibria
  7. 33The learning-rate cliff
  8. 36A* pathfinding
  9. 37Braess's paradox
  10. 44Diffie–Hellman key exchange
  11. 45Preferential attachment
  12. 46Aliasing & the Nyquist limit
  13. 47The secretary problem
  14. 49Freeze too fast, stay stuck
  15. 51Cross one line, and its territory closes
  16. 56Catch one error, miss the next
  17. 57Why more processors stop helping
  18. 58Why a busy line explodes
  19. 59The set that's only sure when it says no
  20. 60The fit that memorizes instead of learns
  21. 61When the wire breaks, pick one
  22. 67Better at both, and still better off trading
  23. 69Everyone was consistent. The vote wasn't.
  24. 78Every world map is lying. You get to pick the lie.
  25. 82Your computer can't hold one tenth — you are here

← the cabinet · Thought Toys — a cabinet of explorable explanations. Exhibit 82.