Floating-Point: Why 0.1 + 0.2 ≠ 0.3
How computers squeeze the infinite real number line into 64 bits — and why a little rounding is the price.
On this page
Type 0.1 + 0.2 into almost any programming language's console and you will not get 0.3. You get 0.30000000000000004. This is not a bug in your language, your compiler, or your CPU. It is the direct, predictable consequence of how computers store real numbers — and once you see the mechanism, the "wrong" answer becomes the only sensible one.
The trap: computers do not store decimals exactly#
The intuition to unlearn is that a computer keeps 0.1 as the tidy decimal you typed. It does not. A double (the 64-bit IEEE-754 format used by JavaScript, Python floats, C double, and most everything else) stores numbers in binary scientific notation:
Those 64 bits are split into three fields:
- 1 sign bit — 0 for positive, 1 for negative.
- 11 exponent bits — a whole number from 0 to 2047, stored with a bias of 1023 so it can represent negative exponents. The actual power of two is .
- 52 mantissa bits — the fractional digits after an implicit leading 1. Because a normalized binary number always starts with a 1, that bit is not stored; you get 53 bits of precision for the price of 52.
Toggle the bits above (the widget uses a 32-bit float — 1 + 8 + 23 — for legibility, but the doubling logic is identical for doubles). Watch how the exponent slides the value between binades (ranges ) while the mantissa fine-tunes the position inside each one.
Here is the crux. The mantissa can only encode fractions whose denominators are powers of two: halves, quarters, eighths, and so on. In decimal, 0.1 terminates. In binary it does not — it is the repeating fraction , exactly the way repeats in decimal. Since the mantissa is finite, 0.1 must be rounded to the nearest representable value. The double you actually get is about 0.1000000000000000055511151231257827. Same story for 0.2.
Why 0.1 + 0.2 misses#
Now add two numbers that were already slightly off:
Step through it. 0.1 snaps to its nearest double (a hair high). 0.2 snaps to its nearest double (also a hair high). Their exact sum is rounded again to the nearest representable value — and that value is 0.30000000000000004, which sits exactly one ulp (unit in the last place) above the double nearest to 0.3. The number line zoom makes it vivid: 0.3 and 0.1 + 0.2 land on two different, adjacent ticks of the representable grid. They are neighbors, but they are not the same bit pattern, so 0.1 + 0.2 == 0.3 is false.
Nothing here is random. Run it a billion times and you get the identical result every time. Which brings us to the second misconception.
Floating-point error is deterministic, not a glitch#
It is tempting to think of floating-point error as noise — little random glitches that creep in. It is the opposite. IEEE-754 specifies round-to-nearest as a precise, reproducible operation: every basic arithmetic result is defined to be the exact mathematical answer rounded to the closest representable value (ties go to even). The same inputs always produce the same output on any conforming machine. The error is not chaos; it is a well-defined, bounded rounding.
What does change is how large that rounding can be, because representable numbers get sparser as magnitude grows. Between 1 and 2, doubles are spaced about apart. Between 2 and 4, the spacing doubles. Between 4 and 8 it doubles again. Each time the exponent ticks up by one, the gap — the ulp — doubles:
The mantissa always carries the same 53 significant bits, so what stays constant is not absolute precision but relative precision, pinned near machine epsilon, . That is why a double can represent numbers up to yet cannot tell 9{,}007{,}199{,}254{,}740{,}992 from the next integer up: past , consecutive integers are no longer all representable.
The practical rules that follow#
Three consequences fall straight out of the mechanism:
Never test floats with ==. Two computations that are mathematically equal can land on neighboring ticks. Compare with a tolerance instead — abs(a - b) < 1e-9 for an absolute check, or a relative epsilon for values that span many magnitudes. This is the same idea as knowing not to expect two different routes through a computation to agree to the last bit.
Beware catastrophic cancellation. Subtracting two nearly equal numbers annihilates their leading significant digits and promotes the tiny rounding errors in their trailing digits to prominence. Computing directly for large loses precision badly; algebraically rewriting it as sidesteps the cancellation entirely. The bits were never wrong — the order of operations exposed them.
Know the special values. IEEE-754 reserves patterns for the edges: +∞ and −∞ (overflow, or 1.0/0.0), NaN (not-a-number, from 0.0/0.0 or sqrt(-1)), and both +0 and −0 (a signed zero that remembers the direction a value approached zero from). NaN is famously not equal to anything, including itself — NaN == NaN is false, which is actually the standard way to test for it.
So what is a computer good at?#
Integers, for one — a 64-bit integer counts exactly up to with no rounding, which is why money is often stored in integer cents, not floating-point dollars. And for the vast landscape of scientific and graphics computation, ~16 significant decimal digits of deterministic precision is plenty; you simply respect the tolerance. Floating-point is not a flawed approximation of "real" arithmetic — it is an astonishingly good engineering compromise that squeezes a usable slice of the infinite real line into a fixed 64 bits. The representable grid is the same idea you meet whenever a continuous world is stored digitally, from the discrete keys of a hash table to the yes/no thresholds of a binary search to the on/off certainty of logic gates.
The lesson is not "computers are bad at math." It is "computers do exactly the math you asked for, in binary, and rounded to the nearest bit — so ask accordingly."
- A
doublestores numbers as : 1 sign bit, 11 biased exponent bits, and 52 mantissa bits after an implicit leading 1 (53 bits of precision). - Many decimals that terminate (like
0.1) are repeating fractions in binary, so they are rounded to the nearest representable value before any arithmetic even begins. 0.1 + 0.2gives0.30000000000000004because two pre-rounded values are added and rounded again — landing one ulp away from the double nearest0.3.- Float error is deterministic round-to-nearest, not random noise; the same computation always yields the same result, and the gap (ulp) doubles each binade, keeping relative precision near machine epsilon (~2.2e-16).
- Never compare floats with
==— use a tolerance; watch for catastrophic cancellation; and handle the special values±∞,NaN(never equal to itself), and signed zero.
Share this article