Failure Modes Float Drift → Integer Cents

Three dimes.
Not thirty cents.

Binary floats cannot store a dime: 0.1 has no exact representation. Charge three of them: the float ledger drifts and its checks start failing, while integer cents stay exact forever.

charged $0.00

Naive, sums doubles

balance
$0
ledger amount DOUBLE
stored 0
displayed $0.00
expected 0
stored == expected

Integer cents, sums exactly

balance
$0
ledger amount_cents BIGINT
stored
displayed $0.00
expected
stored == expected

Charge a few dimes: both ledgers just add ten cents each time. Watch the third one.

How integer money actually works read more

One tenth is a repeating fraction in binary, so a double stores the nearest representable number instead. The display rounds the drift away, which is exactly what makes it dangerous: the checks money runs on do not round. Equality, reconciliation against the processor, debits-equal-credits: all of them fail on a difference too small to print.

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 minor units make every add and compare exact, in every language and database. When fractional precision is inherent, like FX rates, use DECIMAL, which is exact too. The one wrong answer is binary floating point.

The cost: formatting moves to the display layer, and minor units are per currency (yen has zero decimals, Bahraini dinar has three), so the amount travels with its currency code.