Why you never use floats for money
A ledger built on floating point balances today and drifts by cents a year from now.
The short answer
4 things that decide this
- 01Binary floating point cannot represent 0.10 exactly, so every amount you store is already slightly wrong.
- 02Store money as an integer count of minor units, such as 1099 for £10.99, or as a fixed-precision decimal type.
- 03Postgres NUMERIC, Java BigDecimal and Python Decimal all hold exact values; float and double do not.
- 04Correct storage does not save you. Division for splits, tax and currency conversion still needs an explicit rounding rule.
What actually goes wrong
Computers store floats in base two. One tenth in base two repeats forever, the same way one third repeats in base ten. The value gets cut off to fit, so 0.1 becomes something a fraction above or below.
Add 0.1 and 0.2 in almost any language and you get 0.30000000000000004. That looks harmless. It is not, because the error is not random: it repeats in the same direction every time you touch the same value.
One invoice is fine. A million rows of them, summed for a monthly reconciliation, is where the drift becomes a number an accountant has to explain.
What to store instead
Integer minor units are the simplest correct choice. Hold £10.99 as 1099 and divide only when you display it. Arithmetic stays in whole numbers, so nothing can round behind your back.
Fixed-precision decimals are the other option. Postgres NUMERIC and its equivalents hold the digits you asked for and refuse to invent more. They are slower than integers and worth it on anything that has to balance.
- 01Integers suit payments, where two decimal places cover almost every currency.
- 02Decimals suit rates and interest, where you need more places than a currency has.
- 03Currencies vary: the Japanese yen has none, the Jordanian dinar has three. Store the exponent with the amount.
What each storage choice actually does
Same amount, four ways to hold it. Only two of them survive a million rows.
| Approach | Holds 0.10 exactly | Safe to sum | Use it for |
|---|---|---|---|
| float / double | No | No | Nothing involving money |
| Integer minor units | Yes | Yes | Payments, balances, invoices |
| NUMERIC / DECIMAL | Yes | Yes | Rates, interest, tax |
| String | Yes | No | Transport only, never arithmetic |
- Input10.99 typed by a person.
- StoreHeld as 1099, exact.
- ComputeWhole-number arithmetic only.
- RoundOne stated rule, one place.
- DisplayDivided back at the edge.
Rounding happens once, where you decide it does. A float rounds everywhere and tells you nothing.
Which type should you store it in?
Two questions. The answer is one of two types, and the wrong one is expensive to change later.
How many decimal places do you need?
What touches the number most?
Does it cross a currency boundary?
Every outcome
- Integer minor units
- Hold 10.99 as 1099 and divide only when you display it. Arithmetic stays in whole numbers, so nothing rounds behind your back.
- Fixed-precision decimal
- Postgres NUMERIC or the equivalent. Slower than integers and correct at more decimal places, which is what rates and tax need.
Related questions
01Does JavaScript have this problem?
Yes, and more than most. Every JavaScript number is a double, so 0.1 + 0.2 returns 0.30000000000000004 in the browser and in Node. Use BigInt for minor units, or a decimal library, and never let a currency value exist as a plain number.
02Does the database column type save me?
Only for storage. A NUMERIC column holds the value exactly, but if your application reads it into a float, computes a total and writes it back, the error enters in the middle. The type has to be correct end to end.
03What about currencies with three decimal places?
Store the exponent alongside the amount rather than assuming two places. The Jordanian dinar and Tunisian dinar use three, and the Japanese yen uses none. Hardcoding cents breaks the moment you take a payment in Amman.

