Floating-Point Money Bugs and Rounding in Code
Why floats break money, how to use integer minor units or decimals, rounding modes, allocating remainders and tests that catch bugs.
Editorial disclosure: Pyalm publishes and maintains the products and free tools discussed on this site. Regulatory statements are linked to primary sources where applicable. No independent professional review is claimed unless a reviewer is explicitly named.
0.1 + 0.2 !== 0.3. Everybody sees this once and forgets it until a customer's total is one fils off. Money is a domain where "nearly right" is a bug.
Why floats fail
Binary floating point (IEEE 754 doubles) can't represent most decimal fractions exactly. 0.1 is stored as a repeating binary fraction, so arithmetic accumulates tiny errors:
0.1 + 0.2 // 0.30000000000000004
1.005 * 100 // 100.49999999999999
(1.005).toFixed(2) // "1.00" — not "1.01"
Add them up over thousands of invoice lines and you get totals that don't tie to the cent.
Three safe approaches
- Integer minor units. Store AED 12.50 as
1250(fils). Add, subtract and compare integers exactly. Convert to display only at the edge. This is the most common approach. - Decimal types. Use
DECIMAL/NUMERICin the database and a decimal library in code (Java BigDecimal, Python decimal, decimal.js or big.js in JavaScript). - Strings for transport. In JSON, send amounts as strings ("12.50") or integer minor units so no client parses them as floats.
In SQL, use NUMERIC(19,4) or integer columns — never FLOAT or DOUBLE for money.
Currency exponents
Don't hard-code 100. AED, USD, EUR have two decimal places; JPY has zero; KWD, BHD and OMR have three. Keep the currency code with every amount, and look up the exponent.
Rounding modes
| Mode | Behaviour | Where used |
|---|---|---|
| Half up | 2.5 → 3, 1.5 → 2 | Most invoices and tax |
| Half even ("banker's") | 2.5 → 2, 1.5 → 2 | Statistical accuracy, some financial systems |
| Down / truncate | Drops the fraction | Some fee rules |
| Up (ceiling) | Always up | Some tax rules |
Check what the tax authority, your bank or your contract requires — and apply it in one place. Note that JavaScript's Math.round rounds half toward +∞ (so -2.5 → -2), and toFixed operates on the binary value, which causes the 1.005 surprise above.
When to round
Round as late as possible in calculations and exactly where a figure is printed or posted. If an invoice line shows a rounded VAT amount, the total must be built from the rounded parts, or customers will find that the numbers don't add up. Decide per line vs per invoice and document it.
Splitting and allocating
Splitting AED 100.00 three ways gives 33.33 × 3 = 99.99. The leftover fils must go somewhere. Use allocation with remainder distribution: compute the base share in minor units (3333), then give the remaining fils one at a time to the first N parts (3334, 3333, 3333). The parts always sum to the total.
function allocate(total, parts) {
const base = Math.floor(total / parts);
let remainder = total - base * parts;
return Array.from({ length: parts }, () => base + (remainder-- > 0 ? 1 : 0));
}
allocate(10000, 3); // [3334, 3333, 3333]
Percentages and VAT
For 5% VAT on 1,999 fils: 1999 × 5 ÷ 100 = 99.95 fils — which has to round to 100 (half up). Do the multiplication on integers first and divide last, with an explicit rounding step. Never compute amount * 0.05 with a float.
Tests that catch money bugs
- Property tests: the sum of allocated parts equals the total; line totals plus tax equal the invoice total.
- Golden examples from real invoices, including awkward fractions (x.xx5).
- Round-trip tests through the database and JSON.
- Large values and negative amounts (credit notes).
- Different currency exponents.
Don't forget the accounting
Correct arithmetic is necessary, not sufficient: balanced journals, tax accounts and reconciliation matter too — see building an invoicing system. Try the VAT Calculator to sanity-check results, and use the JSON Formatter to inspect payloads. For raw business books, Pyalm Books does the arithmetic and posting for you, free during beta.
Need a financial feature built right?
Billing, invoicing, reconciliation and reporting features are where precision matters most. See custom software development or talk to Pyalm.