Floating-Point Arithmetic
Floating-point numbers store a finite, binary approximation of a real number, so most decimal values are already slightly wrong before you do any arithmetic on them, and that error can accumulate.
0.1 + 0.2 == 0.3 returns False in almost every mainstream programming language. Not because of a bug — because 0.1 cannot be written exactly in the binary format computers use for fractional numbers, in the same way cannot be written exactly in decimal. It's not an edge case you can code around once; it's a permanent property of how the number is stored, and it has to be understood, not patched.
The idea: a ruler with finite ticks
Think of a floating-point number as a ruler that isn't infinitely fine. It has a fixed number of tick marks, and those ticks get closer together near zero and farther apart for huge numbers, but there is always a gap between one representable value and the next. Any real number that doesn't land exactly on a tick gets rounded to the nearest one. The standard format, IEEE 754 double precision (the float64 used almost everywhere by default), splits its 64 bits into three parts: 1 bit for the sign (positive or negative), 11 bits for the exponent (how far to shift the point — this is what lets the ruler cover both tiny and enormous numbers), and 52 bits for the mantissa (the significant digits themselves, giving about 15-17 correct decimal digits).
The exponent is exactly why the ticks aren't evenly spaced: a fixed number of mantissa bits covers a relative range near any given magnitude, so numbers near 1.0 have ticks about apart, while numbers near 1,000,000 have ticks roughly a million times coarser. This is a scale-relative error, not an absolute one — the same reason a ruler marked in centimeters is "exact enough" for a room but useless for a chip.
Worked example: why 0.1 + 0.2 isn't 0.3
0.1 in binary is a repeating fraction, just like is a repeating decimal (0.3333...). Written in binary, , forever repeating. With only 52 mantissa bits, the stored value is truncated and rounded to the nearest representable tick, which is very slightly above the true 0.1 — approximately . Likewise, the stored 0.2 is approximately , also slightly above true 0.2.
Add the two stored (already-rounded) values: the sum comes out to approximately , which itself gets rounded to the nearest representable tick — and that nearest tick is not the same tick that 0.3 typed directly rounds to. Print both with enough digits and the mismatch is visible directly:
>>> 0.1 + 0.2
0.30000000000000004
>>> 0.1 + 0.2 == 0.3
False
>>> abs((0.1 + 0.2) - 0.3) < 1e-9
True
The third line is the standard fix: never compare floats with ==; compare within a small tolerance (an absolute or relative epsilon appropriate to the magnitudes involved).
Worked example: error accumulation in a running sum
The single-comparison error above is tiny (about ), but repeated arithmetic can compound it. Summing the same value 10,000 times in a naive loop, versus using a technique that tracks the rounding error separately (Kahan summation), shows the gap:
x = 0.1
total = 0.0
for _ in range(10_000):
total += x
print(total) # 999.9999999999062 -- not exactly 1000.0
print(total - 1000.0) # -9.37...e-11
Trace why: each += rounds to the nearest representable tick, and because 0.1 itself was already an approximation, each addition's rounding error doesn't average out to zero — it drifts, because the running total's magnitude keeps growing while each individual addend's contribution to the last bit gets relatively smaller as the total grows (adding a small number to a much larger one loses some of the small number's precision, since the sum has to round to the larger number's coarser tick spacing). Over 10,000 additions the drift reaches about — small here, but the same mechanism is what makes naive volatility or P&L accumulators drift measurably over a full trading day of tick-by-tick updates if nothing resets or corrects them.
A floating-point number is rounded to the nearest point on a finite, unevenly-spaced ruler, so most decimal values are already approximations before any arithmetic happens, and the error is relative to magnitude, not a fixed constant — which is why you compare floats with a tolerance, never ==, and why summing many numbers of very different sizes accumulates more error than summing similar-sized ones.
Where this shows up
In a quant-dev interview, this appears as "why doesn't 0.1 + 0.2 == 0.3" or "how would you compare two computed Sharpe ratios for equality," and the expected answer is relative-tolerance comparison plus a one-line explanation of binary representation, not "computers are just imprecise." In production, floating-point behavior drives real design decisions: prices are often stored as integer ticks (cents, or an exchange's minimum price increment) rather than floats specifically to get exact arithmetic and exact equality checks; P&L and risk aggregation code sums in a fixed, deterministic order and sometimes uses higher-precision (float64 over float32, or compensated summation) accumulators specifically to bound the drift traced above; and cross-language or cross-system reconciliation breaks that look like "off by a rounding error" are, more often than not, exactly this.
np.float32 has roughly 7 correct decimal digits versus float64's 15-17 — silently using float32 for a large cumulative sum (common when loading data to save memory, see NumPy Arrays and Dtypes) can produce errors visible to the naked eye, not just in the sixteenth digit.
Practice in interviews
Further reading
- Goldberg, What Every Computer Scientist Should Know About Floating-Point Arithmetic (1991)
- IEEE Std 754-2019, IEEE Standard for Floating-Point Arithmetic