Floating-point arithmetic on monetary amounts produces silent rounding errors — 0.1 + 0.2 is 0.30000000000000004 in JavaScript. When tax, discounts, or proration are calculated using float dollars instead of integer cents, the errors compound across transactions. CWE-681 (Incorrect Conversion Between Numeric Types) covers this, and ISO 25010 functional-correctness requires that calculations produce accurate results. Formatting currency with string concatenation ("$" + amount) produces locale-incorrect output (wrong decimal separators, missing grouping) and breaks for non-USD currencies.
Low because floating-point rounding errors in typical SaaS pricing are small per transaction, but compound at scale and can produce incorrect invoice totals.
Store all monetary amounts as integers in the smallest currency unit (cents for USD). Use Intl.NumberFormat for display and integer arithmetic for calculations.
// Store amounts as cents (integer)
const amountCents = 2999 // $29.99 — never store as 29.99
// Display with locale-aware formatting
const display = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(amountCents / 100) // "$29.99"
// Arithmetic stays in integer cents
const taxCents = Math.round(amountCents * 0.08) // 240 — not 2.392...
const totalCents = amountCents + taxCents // 3239 — exact
Stripe's API returns amounts in the smallest currency unit by default — work in those units end-to-end and only convert to display format at the UI layer.
ID: saas-billing.financial-data.currency-handling
Severity: low
What to look for: Check how currency amounts are handled throughout the codebase. Look for: (a) amounts stored as integers in the smallest unit (cents for USD, pence for GBP) vs. floats or decimals, (b) currency display formatting (is Intl.NumberFormat or a formatting library used, or are amounts formatted with string concatenation), (c) whether the currency is hardcoded or configurable, (d) any arithmetic on monetary amounts (addition, multiplication for tax, etc.) that could introduce floating-point precision errors.
Pass criteria: Count every location where monetary amounts are stored or displayed — at least 1 must use integer storage. Monetary amounts are stored as integers (smallest currency unit), displayed using a proper currency formatter (Intl.NumberFormat with style: 'currency'), and arithmetic on amounts uses integer math or a decimal library. The currency code is consistent throughout the codebase.
Fail criteria: Monetary amounts are stored or processed as floats/decimals (risk of floating-point errors). Currency amounts are formatted with string concatenation ("$" + amount). Different currency codes or amount units are used inconsistently.
Skip (N/A) when: No monetary amounts displayed or calculated in the application code (amounts are displayed exclusively through Stripe-hosted UI elements).
Detail on fail: "Amount stored as float in database — floating-point arithmetic may cause rounding errors in tax or discount calculations" or "Currency formatted as '$ ' + (amount / 100) without locale-aware formatting"
Remediation: Handle currency correctly from the start:
// Store amounts in cents (integer)
const amountCents = 2999 // $29.99
// Display using Intl.NumberFormat
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(amountCents / 100) // "$29.99"
// For arithmetic, keep everything in integer cents
const tax = Math.round(amountCents * 0.08) // 240 cents = $2.40
const total = amountCents + tax // 3239 cents = $32.39
Never use floating-point arithmetic directly on dollar amounts.