Standard Math.round() always rounds 0.5 upward, introducing a systematic positive bias in interest calculations. Over millions of compounding periods this bias is non-trivial and legally relevant — financial regulators and ISO 25010 functional-correctness standards expect rounding to be demonstrably unbiased. Banker's rounding (round-half-to-even) is the industry standard because it distributes rounding error evenly across the dataset. CWE-682 covers this class of incorrect calculation leading to incorrect financial results.
High because systematic rounding bias in interest accrual produces incorrect balances at scale, creating regulatory exposure and customer disputes.
Replace bare Math.round() calls in financial calculation logic with a banker's rounding implementation or a library that supports ROUND_HALF_EVEN. Centralize this in src/lib/finance.ts.
// src/lib/finance.ts
import Decimal from 'decimal.js';
export function roundToNearestCent(value: number): number {
return new Decimal(value)
.toDecimalPlaces(2, Decimal.ROUND_HALF_EVEN)
.toNumber();
}
Search for all Math.round, .toFixed, and Math.ceil calls in src/ and replace with roundToNearestCent() for any interest, APY, or fee calculation.
ID: finserv-form-validation.currency-amount.bankers-rounding
Severity: high
What to look for: Count all rounding call sites in the codebase (search for Math.round, Math.floor, Math.ceil, .toFixed, toDecimalPlaces, ROUND_HALF_EVEN). For each, classify whether it uses banker's rounding or standard rounding. Quote the actual rounding expression found (e.g., Math.round(interest * 100) or Decimal.ROUND_HALF_EVEN). Report the ratio: "X of Y rounding call sites use banker's rounding."
Pass criteria: At least 100% of interest, APY, and fee rounding call sites use banker's rounding (round-half-to-even) or a financial library (decimal.js, big.js, dinero.js) with ROUND_HALF_EVEN. No more than 0 financial rounding sites use Math.round(). Report even on pass: "X rounding call sites found, all using banker's rounding."
Fail criteria: Any financial rounding site uses standard Math.round() (round-half-up), or no rounding strategy is documented for calculations that produce fractional cents.
Must not pass when: A financial library is imported but rounding mode is not explicitly set to ROUND_HALF_EVEN — default modes vary by library.
Skip (N/A) when: The project has no interest, APY, or fee calculations (0 rounding call sites found in financial logic after searching src/, lib/, utils/).
Detail on fail: Describe the rounding issue. Example: "Interest calculation uses Math.round() (round-half-up). At scale, this biases results upward. Should use banker's rounding." or "APY calculation has no rounding strategy documented"
Cross-reference: The Calculation Accuracy category's interest-rounding-consistency check verifies rounding is applied consistently across all locations.
Remediation: Banker's rounding eliminates systematic bias. In src/lib/finance.ts or equivalent calculation utility:
Using decimal.js in src/lib/finance.ts:
import Decimal from 'decimal.js';
const interestRate = new Decimal('0.045'); // 4.5% APY
const principal = new Decimal('1000.00');
const interest = principal.times(interestRate).toDecimalPlaces(2, Decimal.ROUND_HALF_EVEN);
Manual banker's rounding in src/lib/finance.ts:
function bankersRound(value) {
const shifted = value * 100;
const floor = Math.floor(shifted);
const frac = shifted - floor;
if (frac < 0.5) return floor;
if (frac > 0.5) return floor + 1;
// Exactly 0.5: round to nearest even
return floor % 2 === 0 ? floor : floor + 1;
}
const roundedCents = bankersRound(15.505); // Returns 1551 (15.51)