Failure Modes Rounded Split → Largest Remainder

Split a hundred.
Lose a penny.

Splitting money across lines means rounding each share, and the leftover cents vanish. Split a few: the naive lines never sum back to what was charged, while largest remainder reconciles to the cent.

into charged $0.00

Naive, round each line down

in lines
$0
lost
$0
receipt invoice_lines.amount_cents
invoice 10000
sum 0
lost
every cent accounted

Largest remainder, spread the cents

in lines
$0
lost
$0
receipt invoice_lines.amount_cents
invoice 10000
sum 0

Split $100 a few times, then change the divisor: watch whether the receipt still sums to what was charged.

How largest-remainder allocation actually works read more

$100.00 does not divide by 3. The naive split gives every line the same rounded share, so the lines sum to $99.99 and a cent belongs to nobody. One cent sounds harmless; at invoice volume it is a reconciliation break that auditors will find.

Largest remainder makes the rounding explicit: round every share down, then hand the leftover cents, one each, to the first lines:

const base = Math.floor(total / n);  // 10000 / 3 -> 3333 cents, $33.33
const left = total - base * n;       // 1 cent has no home yet

// hand the leftover cents, one each, to the first lines
const lines = Array(n).fill(base).map((c, i) => (i < left ? c + 1 : c));
// -> $33.34, $33.33, $33.33   (sums to $100.00 exactly)

The lines always sum exactly to the total, whatever the divisor. The receipt shows the raw cents on purpose: the money here is already stored as integer cents, and the penny still gets lost. Integer storage makes every sum exact, so division is the only place rounding can happen; allocation is how you handle that one place. Storing money in floats breaks earlier and worse, and that is its own failure mode.

The cost: someone gets the extra cent, and that is a policy you must choose and document (first line, largest line, the fee line), because whoever it is, the same split must always produce the same lines.