Type 0.1 + 0.2 into almost any language and you get 0.30000000000000004. This is the most reported non-bug in computing. The floating-point unit is working perfectly; the problem is that you asked it to store a number it physically cannot represent.

Computers Count in Binary Fractions

A float stores numbers as binary fractions — sums of powers of two:

text
   0.5    = 1/2           = 0.1   in binary
   0.75   = 1/2 + 1/4     = 0.11  in binary
   0.625  = 1/2 + 1/8     = 0.101 in binary

Any value that’s a sum of powers of two fits exactly. The trouble: 0.1 is not. In binary it’s a repeating fraction, like 1/3 is in decimal:

text
   0.1 (decimal) = 0.0001100110011001100110011… (binary, forever)

The hardware has finite bits (53 of significand in a 64-bit double), so it stores the closest representable value — slightly off. Add two slightly-off numbers and the errors surface in the last digits.

IEEE 754 in One Picture

A double splits its 64 bits like scientific notation in binary:

text
   ┌─┬───────────┬────────────────────────────────────┐
   │S│ exponent  │            significand             │
   │1│   11 bits │              52 bits               │
   └─┴───────────┴────────────────────────────────────┘
    sign  scale (×2^e)     the fractional digits

   value = (-1)^S × 1.significand × 2^(exponent − bias)

Roughly 15–17 significant decimal digits of precision. Plenty — until you assume it’s exact.

The Rules That Follow

Never test floats for equality. 0.1 + 0.2 == 0.3 is false. Compare within a tolerance instead:

c
if (fabs(a - b) < 1e-9)   // "close enough", not exact

(Real code should scale the tolerance to the magnitude of the values — an absolute epsilon is wrong for very large or very small numbers.)

Precision degrades with magnitude. The 53 bits are relative, so big numbers have coarse spacing. Past 2^53, integers themselves stop being representable — 9007199254740993.0 rounds to an even neighbor.

Don’t use floats for money. Repeated rounding accumulates. Use integer cents, or a decimal type that represents base-10 fractions exactly.

When You Need Exactness

  • Money / accounting → integers (cents) or a decimal library.
  • Comparisons → tolerance-based, scaled to magnitude.
  • Exact rationals → a big-rational type that stores numerator/denominator.

Floats are a deliberate trade: enormous range and speed in exchange for exactness. For physics and graphics that’s perfect; for a ledger it’s a liability.

Takeaways

  • Floats store binary fractions; decimals like 0.1 don’t fit, so they’re rounded to the nearest representable value.
  • IEEE 754 gives ~15–17 significant digits — relative precision, so large numbers are spaced coarsely (integers break past 2^53).
  • Never compare floats with ==; use a magnitude-scaled tolerance.
  • Use integers or a decimal type for money — never binary floats.