Open this lesson in your favourite AI. It'll walk you through the why, explain the demo, and quiz you on the try-it list.
Storing money as float is the single most common bug in fintech codebases. The right primitive is integer minor units (e.g. cents, paise) bound to a currency code (ISO 4217). Get this wrong once and you'll spend years finding rounding errors hidden in 0.1+0.2 != 0.3 across millions of transactions.
Why integer minor units, not floats.
Use these three in order. Each builds on the one before.
In one paragraph, explain why money should be stored as integer minor units + currency code.
Walk me through how floating-point representation breaks for monetary arithmetic — be specific about which operations fail.
Given a multi-currency wallet for 50 currencies (mix of 0-decimal yen and 3-decimal Bahraini dinar), design the money primitive that handles all of them safely.
// ❌ NEVER do this
const balance = 100.50;
balance + 0.05; // 100.55000000000001 (floating point error)
balance * 1000; // 100499.99999999999 (more drift)
// ✓ ALWAYS use integer minor units + currency code
type Money = { amount: bigint; currency: "USD" | "EUR" | "INR" | "JPY" };
const balance: Money = { amount: 10050n, currency: "USD" }; // $100.50
function add(a: Money, b: Money): Money {
if (a.currency !== b.currency) throw new Error("currency mismatch");
return { amount: a.amount + b.amount, currency: a.currency };
}
function fmt(m: Money): string {
const decimals = CURRENCY_DECIMALS[m.currency]; // USD=2, JPY=0, BHD=3
const div = 10n ** BigInt(decimals);
const whole = m.amount / div;
const fraction = m.amount % div;
return `${whole}.${fraction.toString().padStart(decimals, "0")} ${m.currency}`;
}
// ISO 4217 currency decimals (NOT always 2):
// USD, EUR, INR, GBP: 2
// JPY, KRW: 0 (one yen / one won is the smallest unit)
// BHD (Bahrain): 3 (1 dinar = 1000 fils)
// CLF (Chilean UF): 4
// Persistence:
// Postgres: NUMERIC(20, 0) for amount (integer; no decimal needed)
// VARCHAR(3) for currency code
// MongoDB: amount as Long; currency as enum string
// Never: DOUBLE PRECISION or FLOAT for money
// Display:
// Format AT THE EDGE: convert minor units to display string at API/UI boundary
// Never round inside the system; only at the display layer