Amounts and orders Recommended
Split amounts without losing a cent
When you split an amount, such as instalments or a discount across lines, hand out the leftover minor units in a fixed order. The parts must sum exactly to the total.
How to do it
Never round each part and hope the sum matches. Share the total by ratio in integers, then give the remainder one unit at a time to the first parts. For tax, round once at a documented step, per line or per invoice, and use the same step as your invoices and tax service.
Example
function allocate(totalMinor, ratios) {
const ratioSum = ratios.reduce((sum, ratio) => sum + ratio, 0)
const parts = ratios.map(ratio => Math.floor((totalMinor * ratio) / ratioSum))
let remainder = totalMinor - parts.reduce((sum, part) => sum + part, 0)
for (let i = 0; remainder > 0; i++, remainder--) parts[i] += 1
return parts
}
// allocate(10000, [1, 1, 1]) gives [3334, 3333, 3333]
def allocate(total_minor: int, ratios: list[int]) -> list[int]:
ratio_sum = sum(ratios)
parts = [total_minor * ratio // ratio_sum for ratio in ratios]
for i in range(total_minor - sum(parts)):
parts[i] += 1
return parts
How to check
A property test splits 10,000 random totals by random ratios. The parts always sum to the total, and no part is a whole minor unit or more away from its exact share.
For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers payments playbook: https://agentwranglers.se/playbook/payments/amounts-and-orders/allocate-remainders.