Journal
Money isn't a float
Quick one, but it’s the mistake I see most often from people new to this work: storing
money in a float or double.
The problem is that binary floating-point can’t represent most decimal fractions exactly.
0.1 isn’t 0.1 under the hood — it’s the nearest binary approximation, and the error
compounds. The famous demonstration is that in almost every language,
0.1 + 0.2 doesn’t equal 0.3 (opens in new tab) — you get
0.30000000000000004. That trailing dust is harmless in a physics sim. In a ledger it’s
the difference between a system that balances and one that doesn’t, and
a ledger that doesn’t balance can’t be trusted for anything.
The fix is boring and total: store money as an integer count of the smallest currency
unit. $10.00 is 1000 cents. Do all your arithmetic in those integer minor units, and
only convert to a decimal string at the very edge, when you show it to a human. This is
exactly what mature APIs do — Stripe takes amounts
in the currency’s minor unit with no decimal point (opens in new tab)
(1000 for $10.00), which sidesteps the whole class of bug at the boundary.
Two things the integer rule doesn’t solve on its own. First, minor units aren’t universal:
JPY has zero decimal places, so 1000 there means ¥1000, not ¥10 — you have to know each
currency’s exponent, you can’t hardcode “times 100.” Second, integers don’t rescue you from
division. Split $10.00 three ways and 1000 / 3 is 333 cents each, with a cent left over.
Someone has to get it. The integer just forces you to decide who — instead of letting a
float silently lose the penny where no one’s looking.
That’s the whole point, really. Integers don’t make the hard rounding decisions disappear. They drag them into the open and make you answer them on purpose.
Archie