Floating Point Precision: Why 0.1 + 0.2 Is Not 0.3

June 10, 20269 min read
dsaalgorithmsinterview-prepleetcode
Floating Point Precision: Why 0.1 + 0.2 Is Not 0.3
TL;DR
  • Floating point precision errors come from binary representation: 0.1 has no finite binary form, so the stored value is off by ~10⁻¹⁷, and adding two of these slightly-off values makes the error visible.
  • Never compare floats with ==: use abs(a - b) < 1e-9 instead; two different computation paths to the same mathematical value can produce different bit patterns and fail equality.
  • Float hash map keys are a silent bug: 0.1 + 0.2 and 0.3 have different bit patterns and hash to different buckets even though they look equal.
  • Binary search on real numbers must use a fixed iteration count (100 halvings), not a convergence condition; near a boundary, (lo + hi) / 2 can equal lo or hi and loop forever.
  • sqrt() and log() are O(1) operations: the variable complexity comes from how many times you call them, not from the arithmetic itself.
  • Catastrophic cancellation destroys all significant digits when subtracting two nearly-equal floats; geometry problems with near-coincident points are the main coding interview trap.
  • Prefer integer reformulations: dx² + dy² == r² instead of sqrt(dx² + dy²) == r; integer arithmetic is exact until overflow and sidesteps float comparison entirely.

Three lines that have derailed more interviews than almost anything else:

>>> 0.1 + 0.2 0.30000000000000004 >>> 0.1 + 0.2 == 0.3 False

Not a Python bug. Not JavaScript being weird again. This one belongs to every language, every platform, every computer ever built. It is IEEE 754 floating point, and it has been quietly wrong since 1985. Misunderstanding it will break code you are confident is correct, including code you write under interview pressure.

Short answer: floating point breaks because computers store numbers in binary. Most decimal fractions have no finite binary representation, so the computer stores the closest value that fits in the available bits. Tiny errors compound. Equality comparisons fail silently. Here is why it happens and what to do about it.

Binary Cannot Write 0.1

Think about writing 1/3 in decimal. You get 0.333... and it never terminates. No matter how many digits you write, you cannot express it exactly.

The same problem appears in binary with 0.1. In binary, 0.1 is the repeating fraction 0.0001100110011001100110011..., running forever. A computer cannot store infinity, so it rounds to the nearest value that fits in 52 bits of mantissa.

The value actually stored when you type 0.1 is:

0.1000000000000000055511151231257827021181583404541015625

That looks harmless. Add two of these slightly-off values and the accumulated error becomes visible. 0.1 + 0.2 returns 0.30000000000000004, not 0.3. The moment you first discover this, there are two types of engineers: ones who say "huh, interesting" and move on, and ones who spend the next hour questioning everything they thought they knew about addition.

Why can't floating point just be normal? It returns 0.30000000000000004 instead of 0.3

Every programmer, every time, forever.

What Lives Inside Those 64 Bits

IEEE 754 double precision is the standard your language uses for floating point. Every float in Python, every double in Java, C, or C++, and every number in JavaScript follows this layout:

PartBitsPurpose
Sign1Positive or negative
Exponent11Powers of 2
Mantissa52The significant digits

The value is: (-1)^sign × 2^(exponent - 1023) × 1.mantissa

The 52-bit mantissa gives you roughly 15 to 17 significant decimal digits. Values like 0.1 are stored with an error around 10^-17. Usually invisible. Sometimes not.

For single precision (C's float, Java's float), precision drops to about 7 significant digits, roughly a billion times worse. If you ever see float in production financial code, that is a bug. Someone at that company is explaining a $0.000002 discrepancy to their finance team right now.

The Patterns That Bite You in Interviews

Never Compare Floats with ==

The most common mistake. It shows up in loops, binary search termination, geometry checks, and also in that interview answer where you were this close to a hire signal before silently returning the wrong answer.

# Will silently miss the target for x in nums: if x == target: return True

Use an epsilon comparison instead:

EPSILON = 1e-9 for x in nums: if abs(x - target) < EPSILON: return True

What epsilon? For most interview problems, 1e-9 is fine. If the problem specifies a tolerance like "answer within 10^-5", use half that value.

The same trap catches hash lookups. Two floats that represent the same mathematical value can have different bit patterns if they were computed by different paths, so they hash to different buckets. Your beautiful hash map lookup silently returns nothing. No error. Just vibes.

d = {} d[0.1 + 0.2] = "computed" d[0.3] # KeyError. Different bits, different hash.

Never use raw floats as dictionary keys or set elements. If you need float keys, round to a fixed number of decimal places first, then store as a string or scaled integer.

Your Loop Might Run 11 Times. Or 9. Good Luck.

total = 0.0 count = 0 while total < 1.0: total += 0.1 count += 1 # How many iterations?

You'd expect 10. You might get 10 or 11. Each addition compounds the rounding error slightly differently, and the final value of total can land just above or just below 1.0. The loop does not care about your expectations.

Never drive a loop with a float accumulator. Use integer counts:

for _ in range(10): total += 0.1

Same intent, no surprise termination.

Binary Search on Reals Will Loop Forever Without This Fix

Binary search over a floating-point range is a legitimate interview pattern. You see it on problems like "find the minimum real value x where f(x) >= target." The naive version has a trap:

def binary_search_real(lo, hi, check): while lo < hi: # Can loop forever near a boundary mid = (lo + hi) / 2 if check(mid): hi = mid else: lo = mid return lo

Near the boundary, lo and hi can stop converging. The gap between adjacent representable floats means (lo + hi) / 2 can equal lo or hi exactly, and the loop never terminates. The interviewer is watching. The clock is ticking.

The robust fix is a fixed iteration count. 100 halvings gives you far more precision than any interview problem asks for:

def binary_search_real(lo, hi, check): for _ in range(100): mid = (lo + hi) / 2 if check(mid): hi = mid else: lo = mid return lo

See the binary search invariant guide for the broader framework this fits into.

Subtraction Can Delete All Your Precision

Subtracting two nearly-equal floats destroys precision:

a = 1.0000000000000002 # 1 + 2 × 10^-16 b = 1.0000000000000000 # 1 + 0 a - b # Should be 2 × 10^-16. Actually returns 0.0.

Both values round to the same 64-bit representation, so their difference is zero. All significant digits disappear. This is catastrophic cancellation and it is not subtle when it happens.

In interview geometry problems, this means collinearity tests and distance comparisons can silently give wrong answers when points are nearly coincident. This is why most interview geometry problems specify integer coordinates. If a geometry problem hands you floats, flag it and add an epsilon tolerance to every comparison.

That sqrt() Call Costs Nothing

When you call sqrt(n) or log(n) inside an algorithm, these do not add a complexity factor. Modern CPUs have dedicated floating-point units with hardware instructions for these functions. They run in constant time regardless of n.

This matters when explaining complexity at the whiteboard:

  • Finding all divisors of n: O(√n) because you loop from 1 to √n. The sqrt() call is O(1). The loop that runs √n times is what costs.
  • Binary search: O(log n) because you do log n iterations. The log() function call is O(1).

The variable cost is iteration count, not the arithmetic function call. You may have said "I should cache sqrt(n) to avoid the expensive call" in an interview once. The interviewer wrote something down. Cache it if it improves readability, not for performance.

Avoid the Float Entirely

The cleanest fix for most interview scenarios is to avoid floats completely. Integer arithmetic in computers is exact until overflow (see the integer overflow guide for that side of the coin).

For problems involving distance checks, skip the square root:

# Risky: sqrt introduces float error if sqrt(dx**2 + dy**2) == r: ... # Safe: integer equality, exact if dx**2 + dy**2 == r**2: ...

For problems involving monetary values, store cents not dollars. You will learn this lesson one way or another. For fractions, store numerator and denominator as separate integers.

Almost every "is this computed value equal to X" check involving a float can be reformulated as an integer equality check. This reformulation takes ten seconds to write. It saves you from a 45-minute debugging session where the code looks correct, the tests pass, and the answer is wrong by 0.000000000000004.

Pizza receipt showing $24.990000000000002 due to floating point rounding error in the billing system

Floating point errors respect neither interviews nor pizza orders.

When you spot the integer reformulation in an interview, say it out loud. It signals that you understand the underlying issue, not just the surface symptom.

When You Actually Need Exact Decimals

Python's decimal module and Java's BigDecimal give you exact decimal arithmetic at a performance cost:

from decimal import Decimal Decimal('0.1') + Decimal('0.2') == Decimal('0.3') # True

The string initialization matters. Decimal(0.1) captures the already-imprecise float. Decimal('0.1') parses the string and gets it right.

In interviews, you will almost never need Decimal. It exists for financial software that genuinely cannot tolerate rounding. If an interview problem involves money, the input will typically already be integers representing the smallest denomination.

Five Rules. Commit Them. Don't Debug This Twice.

  • Never compare computed floats with ==. Use abs(a - b) < epsilon where epsilon is 1e-9 unless the problem says otherwise.
  • Never use floats as hash map keys or set elements. Round to integers or strings first.
  • Float loop conditions can terminate early, late, or never. Drive loops with integer counts.
  • Binary search on reals: use a fixed iteration count (100 halvings), not a convergence condition.
  • sqrt(n) and log(n) are O(1) operations. The complexity lives in how many times you call them, not the call itself.

Most interview problems sidestep floating point by using integer inputs. When floats appear, the interviewer usually wants to see you recognize the comparison hazard and handle it. That is the whole test. Recognizing it, naming it, and fixing it in 30 seconds is exactly the signal they are looking for.

If you want to practice these edge cases under actual interview pressure, SpaceComplexity runs voice-based mock interviews where float traps show up in binary search and geometry problems, with rubric feedback on whether you caught them.

Further Reading