checklistmoneynumberslocalizationqa

Testing Money and Numbers: A 35-Point Checklist of Where It All Breaks

Open a console and type 0.1 + 0.2. You’ll get 0.30000000000000004. That’s not a language bug — it’s IEEE 754, binary float, where decimal fractions can’t be represented exactly. Now imagine a balance, a discount, or a tax being computed on that. Money and numbers look like the simplest part of a product — which is exactly why the most expensive bugs live there: a cent lost to rounding, the sum of parts that doesn’t match the whole, “1,234.56” parsed as 1. Here’s what to run.

Money isn’t stored in float

First and foremost: monetary amounts must not live in float/double. Error accumulates, and across thousands of operations “cents” turn into discrepancies. The right way — integers in minor units (cents) or a decimal type. What QA checks:

  • The amount is computed in integer minor units, not float.
  • Repeated operations (credits/debits in a loop) don’t accumulate error.
  • The result matches regardless of addend order (a+b+c = c+b+a).

Rounding — where and how

Rounding is the source of the “lost cent.” Agree on it and verify:

  • Where do we round: on every operation or only on display? Different answers give different totals.
  • How do we round: half-up, half-even (banker’s), down? For money there’s usually a fixed rule — check it’s the same everywhere.
  • Splitting an amount: $100 split by 3 → 33.33 + 33.33 + 33.34; the sum of parts MUST equal the whole, with no lost or extra cent.
  • Percentages: 33% off $10, tax, a fee — rounding must not produce a free item or a negative amount.

Locale formatting

The same number looks different across locales, and that breaks both output and input:

  • Separators: 1,234.56 (US) vs 1 234,56 (RU/FR) vs 1.234,56 (DE). Check both display and input parsing.
  • Currency symbol position: $1,234.56, 1 234,56 ₽, 1.234,56 € — before/after, with or without a space.
  • Negatives: -100, (100), 100-, red — by locale and by requirement.
  • RTL (Arabic/Hebrew): the number and currency sign don’t come apart.
  • Grouping: the Indian system (1,00,000) differs from the Western one (100,000).

Currencies aren’t all the same

  • Decimal places differ: JPY — 0 (¥100, not ¥100.00), USD/EUR — 2, BHD/KWD — 3. Hardcoding “2 places” is a bug for half the currencies.
  • Symbol vs code: $ is ambiguous (US/CA/AU); in reports and multi-currency, the ISO code (USD, EUR) is safer.
  • Conversion: rate, rounding after conversion, stale rate, round-tripping must not “eat” money.

Boundary values

  • Zero: 0.00, free, ”—” instead of zero where appropriate.
  • Negatives: refunds, debts, adjustments — are they allowed, how are they shown.
  • Very large: type overflow, UI truncation, separators on millions/billions.
  • Very small: 0.001, amounts below the minor unit — rounded, not “vanished.”
  • null / empty: not “NaN,” not “undefined,” not 0 instead of “no data.”

User input

An amount field is a source of garbage. Check:

  • Comma vs dot as the decimal separator (the user types by their own habit).
  • Spaces and grouping on paste: pasting 1 000,00 / 1,000.00 must parse, not become 1.
  • Extra zeros (007, 1.500 — is that 1.5 or 1500?), leading/trailing.
  • Letters, symbols, emoji, a minus in the middle — rejected or fixed clearly.
  • More than two decimals in a 2-decimal currency field — what happens.

Business logic: discounts, taxes, fees

  • Discount + tax + fee in one cart — the order of application affects the total; is it fixed?
  • A discount doesn’t push the price below zero; tax is computed from the base or from the discounted amount — per requirement.
  • The cart total = the sum of line items after all rounding (the frequent “lines vs total” desync).
  • A coupon/promo at the edge (discount larger than the amount) doesn’t produce a negative receipt.

Checklist (quick pass)

  • Money in integer minor units or decimal, not float.
  • Repeated operations don’t accumulate error; addend order doesn’t matter.
  • One rounding rule everywhere; where we round is fixed.
  • Splitting: sum of parts = the whole, no lost cent.
  • Thousands/decimal separators by locale — both output and input parsing.
  • Currency symbol position and format by locale; RTL doesn’t break.
  • Decimal places by currency (JPY 0, USD 2, KWD 3), not a hardcoded “2.”
  • Symbol vs ISO code where ambiguity/multi-currency is possible.
  • Conversion: rate, rounding, round-trip without loss.
  • Boundaries: 0, negatives, huge (overflow), tiny, null.
  • Input: comma/dot, spaces, paste, extra zeros, letters.
  • Discounts/taxes/fees: order, not below zero, total = sum of items.

In short — what to take with you

  • 0.1 + 0.2 ≠ 0.3: money isn’t in float — integer minor units or decimal.
  • Rounding is the main source of the “lost cent”; fix the rule and test splitting.
  • Formatting and parsing depend on locale: separators, currency symbol, RTL.
  • Currencies have different decimal places — a hardcoded “2” is a bug.
  • Test boundaries (0, negative, overflow, tiny) and dirty input.

Further reading: 0.30000000000000004.com — on float · Martin Fowler — Money pattern · MDN — Intl.NumberFormat · ISO 4217 — currency codes and symbols