Interview Prep Zoneby CuriouserLabs

Question 2 of 5 · senior level

Why doesn't 0.1 + 0.2 equal 0.3? Explain IEEE 754 and the rules for using floats safely.

🎤 Say this first

IEEE 754 stores numbers as sign × mantissa × 2^exponent — binary scientific notation. 0.1 in binary is infinitely repeating (like 1/3 in decimal), so it's stored rounded; 0.1+0.2 accumulates two roundings into 0.30000000000000004. Doubles give ~15–17 significant decimal digits, and spacing between representable numbers grows with magnitude — above 2⁵³, adding 1.0 does nothing. Rules: never == compare (use epsilons appropriate to scale), never floats for money (BigDecimal/long cents), beware summation order (Kahan/sorted summation for large series), and know the special values: ±0, ±∞, and NaN (which isn't equal to itself — the Double.compare/equals asymmetry in Java).

The full picture

The float pitfall bingo card
0.1 + 0.2 == 0.3                      // false
0.1 + 0.2                             // 0.30000000000000004

double big = 9_007_199_254_740_992.0; // 2^53
big + 1 == big                        // true — spacing is now 2.0

Double.NaN == Double.NaN              // false (IEEE rule)
Double.valueOf(Double.NaN).equals(Double.NaN)  // true! (Java containers need it)
double x = -0.0; x == 0.0             // true, but 1/x = -Infinity

// Money: never double.
BigDecimal price = new BigDecimal("19.99");   // STRING ctor — new BigDecimal(19.99)
                                              // captures the double's error!
long cents = 1999L;                           // or integers in minor units
  • The mental model that generates all the rules: floats are fixed relative precision — ~16 digits wherever you are on the number line; absolute error grows with magnitude. Epsilon comparisons must therefore be relative (or ULP-based), not a fixed 1e-9.
  • Summation order matters: adding tiny numbers to a big accumulator loses them one at a time (catastrophic when averaging millions of values) — Kahan summation or pairwise/streaming approaches; DoubleStream.sum() already does better-than-naive summation.
  • Catastrophic cancellation: subtracting nearly-equal numbers annihilates significant digits — the quadratic-formula rewrite is the textbook case; variance computed as E[X²]−E[X]² going negative is the one you'll actually meet in metrics code.
  • Bonus currency: float16/bfloat16 exist because ML traded precision for throughput — knowing bfloat16 keeps float32's exponent range with 8 mantissa bits shows the trade-off thinking transfers to 2026 problems.

🔄 Likely follow-up questions

  • How would you compare two doubles 'correctly' in a test assertion?
  • Why does (a+b)+c ≠ a+(b+c) matter for parallel reduction (parallel streams!)?
  • When are float32/bfloat16 acceptable, and what breaks first?