Why floating point lies to you

Why 0.1 + 0.2 isn't 0.3, what IEEE 754 actually stores, and how to handle money without accumulating error.

>>> 0.1 + 0.2
0.30000000000000004

This is not a bug in your language. It is the arithmetic working exactly as specified, and the specification is a compromise you should understand before you put it near money.

Binary fractions cannot represent 0.1

In base 10, one third is 0.333… — infinite, because 3 does not divide 10. In base 2, one tenth has the same problem: 0.1 in binary is 0.0001100110011… repeating forever, because 10 has a factor of 5 and binary only has 2s.

A double gets 53 bits of significand, so it stores the closest representable value and stops. That value is not 0.1; it is approximately 0.1000000000000000055511151231257827. Add two such approximations and the small errors survive into the result.

The rule underneath: a fraction is exact in binary only when its denominator is a power of two. 0.5, 0.25 and 0.125 are exact. 0.1, 0.2 and 0.3 never are.

Never compare floats with ==

Since results carry tiny errors, exact comparison is unreliable. Compare within a tolerance:

abs(a - b) < 1e-9          # absolute — fine near zero, wrong for large values
abs(a - b) <= 1e-9 * max(abs(a), abs(b))   # relative — scales properly

Absolute tolerance fails for big numbers, where the gap between adjacent representable doubles is itself larger than your epsilon. Relative tolerance fails near zero. Robust comparisons use both, which is what library functions like Python's math.isclose already do — use those rather than rolling your own.

Error accumulates

One rounding error is invisible. A million are not. Sum a million values of 0.1 naively and the result drifts measurably from 100,000, because each addition rounds again and the errors correlate.

Worse, floating-point addition is not associative: (a + b) + c can differ from a + (b + c). This is why parallel reductions can give different answers run to run depending on chunk scheduling — the results are all equally "correct", which is exactly the problem when you need reproducibility.

If you need an accurate sum of many floats, compensated summation (Kahan's algorithm) tracks the lost low-order bits and adds them back, for roughly the cost of one extra addition per element.

Money

Do not store money in floating point. The two defensible options:

Integer minor units. Store pennies, cents, satoshis. £19.99 is the integer 1999. Addition and subtraction are exact. You handle scaling yourself, and you must decide explicitly how division rounds — which you should be doing anyway, because tax and interest rules specify rounding and your language's default is not the rule.

A decimal type. DECIMAL/NUMERIC in databases, BigDecimal, Python's decimal. These represent base-10 fractions exactly, which is what accounting assumes. Slower than hardware floats, and irrelevantly so for anything that also talks to a database.

The failure mode is not dramatic. It is a total that is one penny out, in a report nobody reconciles for six months, and by then it is in fifty thousand rows.

Where floats are the right answer

Everywhere the input is already approximate: graphics, physics, statistics, machine learning, sensor data. Doubles carry roughly 15–17 significant decimal digits, which comfortably exceeds the precision of nearly any measurement. The problem is never floats themselves — only floats used for exact quantities.