Paid in full.
Still owes $-0.00.
A 30¢ invoice, paid in three 10¢ installments. Computers store 0.1 the same way you'd write 1/3 in decimal: it never divides evenly. Add three of them and you get 0.30000000000000004, not 0.3.
Naive, sums doubles
- invoice
- UNPAID
Integer cents, sums exactly
- invoice
- UNPAID
Pay off the 30¢ invoice in 10¢ installments and watch each system settle it. The customer paid in full, but the naive invoice's remainder never hits exactly zero.
How integer money actually works read more
0.1 has no exact binary form, so the computer stores the closest value it can. Every mainstream language works the same way, so they all land on 0.30000000000000004 for three dimes.
The display rounds that away, which is what makes it dangerous: money code checks for an exact zero, not a rounded one. Rounding doesn't fix the drift, it just hides it. Enough payments, and it crosses a real cent.
The fix is the schema, not the arithmetic:
amount DOUBLE -- 0.1 + 0.1 + 0.1 = 0.30000000000000004
amount_cents BIGINT NOT NULL -- 10 + 10 + 10 = 30, exactly, forever Integer cents make every add and compare exact, in any language, in any database. Need real fractions, like an FX rate? DECIMAL is exact too. Floating point is the one that isn't.
The cost: formatting moves to the display layer, and the decimal count depends on the currency (yen has none, Bahraini dinar has three), so the amount has to travel with its currency code.