Never store money as a float

Floating-point numbers cannot represent most decimal fractions exactly. For a general calculation that's a rounding curiosity; for money, it's a bug that silently drifts real amounts over time.

Beginner

3 min read

The actual problem, demonstrated

0.1 + 0.2 === 0.30000000000000004   // in nearly every mainstream language

Binary floating-point (float/double) represents numbers as sums of powers of two, and most decimal fractions — including something as simple as 0.10 — have no exact binary representation, the same way 1/3 has no exact finite decimal representation. Every arithmetic operation on a float carries a small representational error. For scientific computing that error is usually irrelevant. For money, it compounds: run enough additions, multiplications, and currency conversions on floats, and a customer's actual balance can drift by real cents from what every receipt says it should be.

Two safe alternatives, both seen in real systems

Integer smallest-unit (cents)  - store 1050 to mean $10.50. All
                                  arithmetic is plain integer math,
                                  which is exact. Divide by 100 only
                                  at the very edge, for display.

Fixed-point Decimal type        - a dedicated arbitrary-precision
                                  decimal type (SQL's DECIMAL/NUMERIC,
                                  a language-level Decimal class),
                                  storing 10.50 as an exact decimal
                                  value rather than a binary
                                  approximation.

Both are legitimate, widely-used choices, and it's common to see both in the same system for different reasons: a payment gateway's own API frequently speaks in integer cents natively (amount_cents: 1050 is a typical field on a gateway's request/webhook payload) precisely because integers avoid the floating-point trap at the wire-protocol level. A database schema storing invoices and ledger balances, on the other hand, commonly uses a DECIMAL(19,4)-style column — explicit fixed precision and scale, so a currency conversion at checkout is copied faithfully into the persisted record, not silently reinterpreted as a float along the way.

// Typical boundary conversion, gateway integer-cents -> DB Decimal
const amountCents = webhookPayload.amount_cents;      // 1050
const invoiceAmount = new Decimal(amountCents).div(100); // 10.50, exact

Why "just round it at the end" doesn't fix anything

Rounding a float at display time hides the error in the UI, but the underlying stored value is still imprecise — the next calculation performed on it (a partial refund, a tax computation, a currency conversion) compounds from the already-wrong number, not from the true one. The fix has to happen at the storage layer, not the display layer.

The concrete rule

A monetary amount is either an integer count of the smallest currency unit, or a fixed-precision decimal type — never a plain float/double, in any column, any variable, any API payload a system controls the shape of. The only float-typed money a system should ever touch is one arriving from a third party it doesn't control the shape of, and even then, the very first thing to do with it is convert to one of the two safe representations before it touches any arithmetic.

Not every currency uses two decimal places. Japanese yen (JPY) has zero minor units; some currencies use three. A gateway's amount_cents-style field is really "amount in the currency's smallest unit," and dividing by a hardcoded 100 for every currency is a real, recurring bug in systems that expand beyond a single currency — the correct divisor depends on the currency itself, not a universal constant.

Further reading

Check your understanding

A quick comprehension check — not tracked, not graded, just for you.

1. Why does 0.1 + 0.2 not exactly equal 0.3 in most programming languages?

2. A system stores an invoice amount as 1050 in an integer column, representing $10.50. Why might this specific approach be used?

3. A developer stores money as a float but rounds it to 2 decimal places every time it's displayed. Does this solve the precision problem?

4. A system expanding to support Japanese yen (JPY) divides every gateway amount-in-smallest-unit field by a hardcoded 100 to get the display amount. What's wrong with this for JPY specifically?