CREATE TABLE payments (id int, amount float);
INSERT INTO payments VALUES (1, 0.1), (2, 0.2);
SELECT SUM(amount) = 0.3 FROM payments; -- false
SELECT SUM(amount) FROM payments; -- 0.30000000000000004
Every few months a finance report is off by a cent, an invoice total doesn't match its line items, or a balance check fails for no visible reason. The cause is usually a FLOAT or DOUBLE/REAL column holding money.
Why floats can't hold money
FLOAT/DOUBLE store numbers in binary floating point (IEEE 754). Just as 1/3 can't be written exactly in decimal, 0.1 can't be represented exactly in binary. It's stored as the nearest binary fraction, 0.1000000000000000055511151231257827…
Each value is only slightly off, but:
- sums drift: add up a million line items and the cents stop matching;
- equality fails:
total = 19.99may not match the stored value; - rounding goes wrong: 2.675 rounded to 2 places gives 2.67, because the stored value is really 2.67499999…;
- results can differ between databases, languages and CPUs, so reconciliations fail.
Floats are right for measurements (temperatures, coordinates, scientific data), where tiny relative errors are acceptable. They're wrong for money, where exact decimal arithmetic is a legal and accounting requirement.
