Split Money Amounts with Decimal Without Loss
Split a Decimal amount across integer weights while keeping cent-level totals exact, distributing leftover cents deterministically.
Code
from __future__ import annotations
from decimal import ROUND_HALF_UP, Decimal
def split_amount(total: str, weights: list[int]) -> list[Decimal]:
amount = Decimal(total).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
if amount < 0:
raise ValueError("amount must be non-negative")
if not weights or any(weight < 0 for weight in weights):
raise ValueError("weights must be non-negative and non-empty")
weight_sum = sum(weights)
if weight_sum == 0:
raise ValueError("weights must not sum to zero")
cents = int(amount * 100)
raw = [cents * weight for weight in weights]
shares = [value // weight_sum for value in raw]
remainder = cents - sum(shares)
order = sorted(
range(len(weights)),
key=lambda index: (-(raw[index] % weight_sum), index),
)
for index in order[:remainder]:
shares[index] += 1
return [Decimal(share) / 100 for share in shares]
def main() -> None:
thirds = split_amount("100.00", [1, 1, 1])
print([str(part) for part in thirds], sum(thirds))
assert sum(thirds) == Decimal("100.00")
uneven = split_amount("0.05", [1, 1, 2])
print([str(part) for part in uneven])
assert [str(part) for part in uneven] == ["0.01", "0.01", "0.03"]
try:
split_amount("10.00", [0, 0])
except ValueError as exc:
print(f"rejected: {exc}")
if __name__ == "__main__":
main()
Report a problem with this exampleOpens a pre-filled issue on GitHub.