Цель округления - генерировать наименьшее количество ошибок. Когда вы округляете одно значение, этот процесс прост и понятен, и большинство людей легко это понимают. Когда вы округляете несколько чисел одновременно, процесс усложняется - вы должны определить, как ошибки будут объединяться, то есть что должно быть сведено к минимуму.
Хорошо проголосовали ответ на Varun Вохра минимизирует сумму абсолютных ошибок, и это очень просто реализовать. Однако есть крайние случаи, которые он не обрабатывает - что должно быть результатом округления 24.25, 23.25, 27.25, 25.25
? Один из них должен быть округлен вверх, а не вниз. Вы, вероятно, просто произвольно выберете первый или последний в списке.
Возможно, лучше использовать относительную ошибку вместо абсолютной ошибки. Округление с 23.25 до 24 изменяет его на 3.2%, а при округлении 27.25 до 28 - только 2.8%. Теперь есть явный победитель.
Это можно настроить еще дальше. Одним из распространенных методов является возведение в квадрат каждой ошибки, так что большие ошибки считаются непропорционально больше, чем маленькие. Я бы также использовал нелинейный делитель для получения относительной ошибки - кажется неправильным, что ошибка в 1% в 99 раз важнее, чем ошибка в 99%. В приведенном ниже коде я использовал квадратный корень.
Полный алгоритм выглядит следующим образом:
- Суммируйте проценты после округления их всех вниз и вычтите из 100. Это говорит о том, сколько из этих процентов нужно округлить вместо этого.
- Создайте две оценки ошибок для каждого процента: один при округлении в меньшую сторону и один при округлении в большую сторону. Возьмите разницу между двумя.
- Сортировка ошибок, полученных выше.
- Для количества процентов, которые необходимо округлить, возьмите элемент из отсортированного списка и увеличьте округленный процент на 1.
У вас может быть несколько комбинаций с одной и той же суммой ошибок, например 33.3333333, 33.3333333, 33.3333333
. Это неизбежно, и результат будет совершенно произвольным. Код, который я даю ниже, предпочитает округлять значения слева.
Собрать все это вместе в Python выглядит следующим образом.
def error_gen(actual, rounded):
divisor = sqrt(1.0 if actual < 1.0 else actual)
return abs(rounded - actual) ** 2 / divisor
def round_to_100(percents):
if not isclose(sum(percents), 100):
raise ValueError
n = len(percents)
rounded = [int(x) for x in percents]
up_count = 100 - sum(rounded)
errors = [(error_gen(percents[i], rounded[i] + 1) - error_gen(percents[i], rounded[i]), i) for i in range(n)]
rank = sorted(errors)
for i in range(up_count):
rounded[rank[i][1]] += 1
return rounded
>>> round_to_100([13.626332, 47.989636, 9.596008, 28.788024])
[14, 48, 9, 29]
>>> round_to_100([33.3333333, 33.3333333, 33.3333333])
[34, 33, 33]
>>> round_to_100([24.25, 23.25, 27.25, 25.25])
[24, 23, 28, 25]
>>> round_to_100([1.25, 2.25, 3.25, 4.25, 89.0])
[1, 2, 3, 4, 90]
Как вы можете видеть из этого последнего примера, этот алгоритм все еще способен давать неинтуитивные результаты. Хотя 89.0 не нуждается ни в каком округлении, одно из значений в этом списке необходимо округлить; самая низкая относительная ошибка является результатом округления этого большого значения, а не намного меньших альтернатив.
Этот ответ первоначально предусматривал прохождение всех возможных комбинаций округления вверх / вниз, но, как указано в комментариях, более простой метод работает лучше. Алгоритм и код отражают это упрощение.