Money bugs are the expensive kind. A rounding error in a to-do app is annoying. In an invoicing or checkout flow, it's a support ticket, a refund, or an audit problem.
Here are six common currency mistakes, with short examples and quick ways to verify your results.
1. Using floats for money
Binary floating point can't represent most decimal fractions exactly.
0.1 + 0.2; // 0.30000000000000004
19.99 * 100; // 1998.9999999999998
Those tiny errors compound across line items, taxes, and conversions.
Fix: store money as integers in the currency's smallest unit (cents, for USD), or use a decimal type.
from decimal import Decimal
price = Decimal("19.99")
qty = 3
total = price * qty
print(total) # 59.97
In JavaScript, keep amounts as integer minor units and only format them for display. When parsing user input, parse the string rather than multiplying a float, because inputs like 1.005 can still round the wrong way.
2. Assuming every currency has two decimals
The US dollar has two decimal places, but the Japanese yen has none, and some currencies, such as the Kuwaiti dinar, use three. Hardcoding * 100 breaks for them.
ISO 4217 defines the number of minor-unit digits for each currency. In JavaScript you can read it from Intl instead of maintaining your own table:
function minorUnitDigits(code) {
return new Intl.NumberFormat("en", { style: "currency", currency: code })
.resolvedOptions().maximumFractionDigits;
}
minorUnitDigits("USD"); // 2
minorUnitDigits("JPY"); // 0
minorUnitDigits("KWD"); // 3
const toMinor = (amount, code) =>
Math.round(amount * 10 ** minorUnitDigits(code));
If you want to double-check a currency's details, the currency information page lists names, countries, and codes, and the world currency list gives you the full picture of currencies in use.
3. Storing symbols instead of codes
The $ sign is shared by several currencies, so a symbol alone doesn't tell you which one you're dealing with. Intl makes this visible:
const fmt = (locale, currency) =>
new Intl.NumberFormat(locale, { style: "currency", currency })
.format(1234.5);
fmt("en-US", "USD"); // $1,234.50
fmt("en-US", "CAD"); // CA$1,234.50
fmt("en-US", "AUD"); // A$1,234.50
fmt("en-CA", "CAD"); // $1,234.50
fmt("de-DE", "EUR"); // 1.234,50 €
Same symbol, different currencies, different formats per locale.
Fix: store and transmit the three-letter ISO 4217 code (USD, EUR, JPY) and let the UI layer decide how to display it. Also avoid hardcoding your own list of valid codes, because the list changes over time. Croatia, for example, replaced the kuna with the euro on January 1, 2023.
Handy references: currency codes for alphabetic and numeric ISO codes, and currency symbols to see which countries use which symbol.
4. Mixing up base and quote currencies
An exchange rate is a pair. In EUR/USD, EUR is the base and USD is the quote, and the rate tells you how many USD one EUR buys. Apply it in the wrong direction and your result is off by a lot.
from decimal import Decimal, ROUND_HALF_UP
amount_eur = Decimal("249.99")
rate_eur_usd = Decimal("1.0835") # illustrative rate, not live data
amount_usd = (amount_eur * rate_eur_usd).quantize(
Decimal("0.01"), rounding=ROUND_HALF_UP
)
print(amount_usd) # 270.86
To go the other way, divide by the rate (or use the inverse pair). Name your variables after the pair (rate_eur_usd) so the direction is obvious in code review.
Keep in mind that real providers quote a buy rate and a sell rate with a spread between them, so the inverse of one quote won't exactly match the other.
For a quick independent check of your math, run the same numbers through a currency converter or an exchange rate calculator.
5. Not storing the rate you used
If you convert an amount and only save the result, you can't explain it later. Rates change throughout the day, and different providers can show different values at the same moment.
Store alongside every conversion:
- Base and quote currency codes
- The rate used
- Where the rate came from
- When it was captured
- The original amount
{
"original": { "amount": "249.99", "currency": "EUR" },
"converted": { "amount": "270.86", "currency": "USD" },
"rate": "1.0835",
"pair": "EUR/USD",
"source": "your-rate-provider",
"captured_at": "2026-10-01T09:30:00Z"
}
For accounting and reports, you often need the rate as of a past date rather than today's. When you need to sanity-check a value, historical exchange rates let you look up how currencies compared over time, and the current exchange rates page covers major currencies today. Always check coverage and date ranges before using any source for formal records.
6. Forgetting that conversion is not the final amount
A converted number is an estimate of what a payment will cost. Banks, card networks, and payment providers can add spreads, fees, and taxes, so the amount actually charged can differ from what your converter shows.
If your product shows prices in another currency, label them as approximate, or show the exact charged amount only once it's known.
The same applies when payments cross borders. Bank identifiers are a common validation task, and a BIC/SWIFT code has a predictable shape: 4 letters for the bank, 2 for the country, 2 alphanumeric characters for the location, and an optional 3-character branch code.
const BIC = /^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$/;
BIC.test("DEUTDEFF"); // true (8 characters)
BIC.test("DEUTDEFF500"); // true (11 characters)
BIC.test("DEUT-DEFF"); // false
A format check only proves the string looks right, not that the bank exists, so verify against a reliable source before sending money. A world banks directory can help you look up bank names, countries, SWIFT/BIC codes, and supported currencies, and the world payment systems reference gives an overview of the card networks, instant payment systems, mobile money, and wallets used around the world.
Quick checklist
- Are amounts stored as integers or decimals, never floats?
- Does the code use each currency's actual minor-unit digits?
- Are you storing ISO codes, not symbols?
- Is the base/quote direction explicit in variable names?
- Is the rate, source, and timestamp saved with each conversion?
- Is the UI honest that converted amounts are estimates?
Free tools for quick checks
When a number looks wrong, I like to confirm it independently before changing code. The Noloii Currency & Finance Tools collection is free and runs in the browser. The ones most useful for developers:
- Currency codes and currency symbols for ISO references
- Currency converter and exchange rate calculator for spot checks
- Historical exchange rates for past-date lookups
- World banks and world payment systems for payment context
What's the worst money bug you've run into? Let me know in the comments.
Top comments (0)