Skip to content

Instantly share code, notes, and snippets.

@snake575
Created May 2, 2017 22:16
Show Gist options
  • Save snake575/dfe40e9bbd073525d1be07e5e4976906 to your computer and use it in GitHub Desktop.
Save snake575/dfe40e9bbd073525d1be07e5e4976906 to your computer and use it in GitHub Desktop.
# https://stackoverflow.com/questions/15769948/round-a-python-list-of-numbers-and-maintain-the-sum
from decimal import Decimal, ROUND_HALF_UP
def round_amounts(amounts, original_amount, decimal_places):
amounts = [Decimal(str(amount)) for amount in amounts]
places = Decimal(10) ** -decimal_places
rounded_list = (
[amount.quantize(places, ROUND_HALF_UP) for amount in amounts])
rounded_list_sum = sum(rounded_list)
error = Decimal(original_amount) - rounded_list_sum
n = int(round(error / places))
new_list = rounded_list[:]
for _, i in sorted(
((amounts[i] - rounded_list[i], i)
for i in range(len(amounts))), reverse=n > 0)[:abs(n)]:
new_list[i] += Decimal.copy_sign(places, n)
_check_amounts_sum(new_list, original_amount)
return new_list
def round_amounts_by_ratio(ratios, original_amount, decimal_places):
amounts = [(Decimal(original_amount * ratio).
quantize(decimal_places, ROUND_HALF_UP))
for ratio in ratios]
amounts_rounded = round_amounts(
amounts=amounts, original_amount=original_amount,
decimal_places=decimal_places)
return amounts_rounded
def _check_amounts_sum(amounts, original_amount):
amounts_sum = sum(amounts)
error = amounts_sum - original_amount
if abs(error) > 0:
message = ('Sum of amounts ({0}) != original ({1})'.
format(amounts_sum, original_amount))
raise ValueError(message)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment