Thought Toys · Computation & information · 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. That perfect tie breaks 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 all round-trip unchanged. So what you're dragging is the same object your browser uses, not a story about it.
  • The exact decimals shown above are confirmed, the gap is exactly one ulp, and the tie is exact.
  • Exactly 4 of the 100 cent amounts from $0.01 to $1.00 are representable.
  • The error never exceeds half an ulp over 9,000 random values at three widths.
  • Negative control: a base-ten float built from the identical code stores 0.1 with zero error and fails on 1/3 instead. The culprit is the base, not floating point.
  • Negative control: truncating instead of rounding-to-nearest breaks the hardware cross-check, so that check has teeth.
  • Negative control: 0.5 + 0.25 and 0.125 + 0.125 are exact at every width tested. The claim is not "floats are wrong."
  • Negative control: printing to two places shows a tidy "0.30" while the comparison still fails — formatting hides this rather than fixing it. Comparing within a tolerance is the fix that works.
The gate corrected me twice while I wrote it. I had claimed the addition itself was exact; it isn't, and the tie-break is what decides this case. And my first exact-decimal printer refused a subtraction that isn't dyadic — the same lesson from the other end. 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 Computation & information: Noise sets a speed limit, not an accuracy limit. →

All 20 in Computation & information
  1. 101Noise sets a speed limit, not an accuracy limit.
  2. 111Any party of six hides a trio
  3. 120How one bit of ink makes a grey
  4. 125The ambulance that arrives sooner by going slower
  5. 131Every number is a sum of Fibonacci numbers
  6. 23Sorting algorithms
  7. 24PageRank & the random surfer
  8. 25Huffman coding
  9. 26Dijkstra's shortest path
  10. 33The learning-rate cliff
  11. 36A* pathfinding
  12. 44Diffie–Hellman key exchange
  13. 49Freeze too fast, stay stuck
  14. 56Catch one error, miss the next
  15. 57Why more processors stop helping
  16. 58Why a busy line explodes
  17. 59The set that's only sure when it says no
  18. 60The fit that memorizes instead of learns
  19. 61When the wire breaks, pick one
  20. 82Your computer can't hold one tenth — you are here

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