Comparing and Adding Floats Safely
A test checks that a discount brings a price to 0.3 and fails with "expected 0.3, got 0.30000000000000004". A physics simulation adds a tiny time step to a huge clock value and the clock stops moving. Both have the same cause: every float result is slightly rounded. You cannot remove the rounding, but you can write code that is not fooled by it.
The rule
Never ask floats for exact equality; ask whether they are close enough.
Instead of a == b, check whether the difference is smaller than a tolerance:
abs(a - b) < 1e-9 # absolute tolerance
math.isclose(a, b) # Python: relative tolerance, scales with sizeAn absolute tolerance like 1e-9 suits numbers near 1. For very large or very small numbers, a relative tolerance (a fraction of the numbers' size) works better, which is what math.isclose does by default.
Three more habits follow from the same rule:
- Money in whole units. Store 1999 cents, not 19.99 dollars. Integer addition is exact. Many languages also offer a decimal type for this.
- Big plus tiny loses the tiny. Near 10¹⁶, neighbouring doubles are 2 apart, so
1e16 + 1rounds straight back to1e16. Adding many small values to a huge one can lose them all. - Order matters. Float addition is not perfectly associative:
(a + b) + ccan differ slightly froma + (b + c). Summing small values first, or usingmath.fsum, reduces error.
The shortcut
Before writing any float comparison, ask two questions: "Could either side have been rounded?" If yes, use a tolerance. "Is this money or a count?" If yes, use integers.
A price total
Money must be exact to the cent: store integer cents, then compare with ==.
Drills
Answer fast — trust the rule.
Which is the safest test that a computed float x equals 0.3?
What is the best way to store prices in a shop's code?
In 64-bit doubles, what does 1e16 + 1 == 1e16 give?
Why is a relative tolerance better than a fixed 1e-9 for comparing values around 10¹²?
Remember
- Never test floats with ==; check they are close enough.
- Money and counts belong in integers, not floats.
- The trap: tiny values added to huge ones vanish, and the order of additions changes the result.
