Decimal vs Float for Money
Ordinary binary floating point cannot represent most decimal fractions exactly, which is fine for prices and returns but dangerous for cash and P&L — where a decimal type or integer cents avoids the drift.
Prerequisites: Floating-Point Arithmetic and Precision
0.1 + 0.2 in Python returns 0.30000000000000004, not 0.3. That's not a Python bug — it's a fundamental fact about binary floating point, which can represent most integers exactly but only a small subset of decimal fractions exactly, because 0.1 and 0.2 have no finite representation in base 2, the same way has no finite representation in base 10. For a scientific computation this rounding is usually harmless noise. For a ledger tracking cash to the cent, it is not.
Binary floats cannot exactly represent most decimal fractions, so repeated addition or subtraction of money amounts stored as float accumulates tiny errors that compound over many transactions. Decimal (base-10 arithmetic) or storing money as integer cents avoids this entirely, at some cost in speed and syntactic convenience.
Why it happens
A binary float stores a number as (sign) x (mantissa) x . Just as has no finite decimal expansion (0.333...), most decimal fractions like have no finite binary expansion, because 10 has a prime factor (5) that isn't a power of 2. The stored value is the nearest representable binary float to 0.1 — extremely close, but not exact. Individually this error is around , invisible in almost any single calculation. The danger is that it doesn't cancel out on average the way random noise would — it's a deterministic bias for each specific decimal value, and repeated arithmetic on the same biased values can accumulate rather than average away.
Worked example
total = 0.0
for _ in range(1_000_000):
total += 0.10
print(total) # 99999.99999999831 — not exactly 100000.00
from decimal import Decimal
total = Decimal("0.00")
for _ in range(1_000_000):
total += Decimal("0.10")
print(total) # 100000.00, exact
The float loop is off by about $0.0000000017 after a million additions — invisible to a human eye on a single printout, but potentially a real discrepancy in a reconciliation report that checks totals to the cent, or in a system that runs this kind of accumulation continuously over years of trading activity. Decimal, which stores numbers as an exact base-10 digit sequence rather than base-2, has no such drift because 0.10 is exactly representable in base 10.
What this means in practice
The common convention in trading and accounting systems is to avoid the whole class of bug by storing money as integer cents (or the smallest currency unit) rather than as a fractional dollar amount at all — integers add and subtract exactly regardless of base — converting to a display format only at the edges. Where fractional arithmetic genuinely can't be avoided (interest accrual, pro-rating), Decimal is the standard choice for anything touching cash balances, even though it's slower and more verbose than native floats, because correctness trumps speed for ledger entries. Prices, returns, and Greeks, by contrast, are typically fine as ordinary floats — a basis point of relative float error in a volatility estimate is immaterial next to the model uncertainty already present.
Never compare money values with == after any float arithmetic, even between two values that "should" be equal — always compare with a tolerance (abs(a - b) < 1e-9) or, better, avoid the whole issue by working in integer cents or Decimal for anything that will be summed, compared, or reconciled to the cent.
Related concepts
Practice in interviews
Further reading
- Goldberg, 'What Every Computer Scientist Should Know About Floating-Point Arithmetic' (1991)
- Python documentation, 'decimal — Decimal fixed point and floating point arithmetic'