Why 0.1 + 0.2 ≠ 0.3: Floating Point and Money in Code
Practice4 minInnovialabs editorial team
In short
Floats store numbers approximately in binary, so money should be kept as integers in minor units (cents, tiyin) or in decimal types such as Decimal and NUMERIC.
The short answer
Most languages store fractional numbers using the IEEE 754 standard — in binary. In binary, 0.1 is an infinite fraction, just like 1/3 in decimal. The computer cuts it off, so memory holds a value that is very close but not exact.
0.1 + 0.2 // 0.30000000000000004
0.1 + 0.2 === 0.3 // false
For scientific work this error usually does not matter. For money it does: a cent lost in one operation becomes a mismatch in a report, and the amount charged no longer matches the receipt.
Rule: never store or calculate money in float or double.
How a float works
An IEEE 754 number has three parts: a sign, a mantissa (the significant digits) and an exponent (where the point goes). A double gives roughly 15–17 significant decimal digits.
This leads to two kinds of problems:
- Unrepresentable fractions. 0.1, 0.2 and 0.7 cannot be written exactly in binary.
- Precision loss on large numbers. When a number is very large, its lower digits simply do not fit in the mantissa.
Errors accumulate: add 0.1 a thousand times and the result is visibly different from 100.
Option 1: integers in minor units
Store the amount as an integer in the smallest unit of the currency: cents for dollars, tiyin for Uzbek so'm.
- 15,000.50 so'm →
1500050tiyin - $19.99 →
1999cents
Integers add and subtract exactly. You convert to a human-readable format only when displaying.
price_tiyin = 1_500_050
qty = 3
total_tiyin = price_tiyin * qty # 4,500,150, exact
print(f"{total_tiyin / 100:,.2f} UZS") # display only
Keep in mind that currencies differ in the number of decimal places. ISO 4217 gives the Uzbek so'm two, although tiyin are rarely seen in cash circulation. Many systems keep so'm amounts in whole so'm — that is fine as long as it is written down in the requirements.
Option 2: decimal types
Decimal types store numbers in base 10 and calculate without binary errors.
| Environment | Type |
|---|---|
| Python | decimal.Decimal |
| Java | BigDecimal |
| C# | decimal |
| PostgreSQL, MySQL | NUMERIC(p, s) / DECIMAL(p, s) |
| JavaScript | third-party decimal arithmetic libraries |
from decimal import Decimal, ROUND_HALF_UP
a = Decimal("0.1") + Decimal("0.2") # Decimal('0.3')
vat = (Decimal("15000.50") * Decimal("0.12")).quantize(
Decimal("0.01"), rounding=ROUND_HALF_UP
)
Create a Decimal from a string, not from a float: Decimal(0.1) inherits the same error.
Rounding is a business rule
Even with exact types, money has to be rounded: taxes, discounts, exchange rates. Decide upfront:
- Rounding mode. Half-up as taught at school, or banker's rounding (half to even). Python's built-in
round(), for example, uses banker's rounding. - When to round. Per receipt line or once on the total — the results can differ.
- Splitting amounts. 100 does not divide evenly by three: 33.33 + 33.33 + 33.34. The remainder is explicitly assigned to one of the parts.
Project checklist
- In the database —
NUMERICor an integer in minor units, neverFLOATorREAL. - In the API — amounts as a string (
"15000.50") or an integer in minor units, with a currency code next to them. - The currency is stored with the amount, not assumed.
- Rounding rules are written into the requirements and covered by tests.
- Exchange rates are stored with enough precision and a date.
- The frontend only displays amounts; the server does the final calculation.
Common mistakes
- Comparing floats with
==. - Using
parseFloaton amounts from a form and then doing arithmetic. - A
moneytype or float in JSON with no agreement on precision. - Rounding at every intermediate step without a rule.