polycratia

The most expensive bug in a payment system is usually off by one cent.

A crash you fix in an hour. A rounding rule that quietly hands out 99.99 instead of 100.00 runs for months, because nothing throws and every single record looks plausible on its own.

The mechanics are dull. You split an amount across several recipients: pro-rata returns to multiple investors in one deal, or fees across parties, or a refund across the original payment legs. That split is a division, and it rarely lands on whole units. Each share gets rounded on its own, so the sum of the rounded shares is no longer the amount you started with. A cent sits unassigned, or a cent gets invented.

Nothing in the request path objects. Rows are written, balances move, and the user sees a credible number. The error surfaces only where something finally checks a conservation invariant: reconciliation against what the bank actually settled. By then the residue is smeared across thousands of records, and a rounding artifact looks the same as a genuine mismatch or a transfer that really did fail. You stop trusting the whole report, not just the bad rows.

So I treat the split as a first-class operation instead of an arithmetic detail. Amounts in minor units as integers, never floats. Shares derived from the total, then the remainder assigned by one explicit, documented rule: largest share, or deterministic order, or whatever the business agrees to, as long as it is written down and tested. And the invariant asserted inside the same transaction as the write: the parts sum to the whole, or nothing is written.

That last line is the actual lesson. A small bug grows into a large production problem when the system has nowhere it is allowed to refuse.

I write up more of these production notes here: https://polycratia.com/c/1e0e5feb

I'd want to know where your system asserts its money invariants: at write time, or in a nightly job...

react

$ new-project --brief

or email hey@polycratia.com