DEV Community

Cover image for 6 Currency Mistakes Developers Make (and How to Avoid Them)
Bellal Hossain
Bellal Hossain

Posted on

6 Currency Mistakes Developers Make (and How to Avoid Them)

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

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 €
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

  1. Are amounts stored as integers or decimals, never floats?
  2. Does the code use each currency's actual minor-unit digits?
  3. Are you storing ISO codes, not symbols?
  4. Is the base/quote direction explicit in variable names?
  5. Is the rate, source, and timestamp saved with each conversion?
  6. 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:

What's the worst money bug you've run into? Let me know in the comments.

Top comments (0)