Мета округлення - генерувати найменшу кількість помилок. Коли ви округляєте одне значення, цей процес простий і простий, і більшість людей його легко розуміють. Коли ви округляєте декілька чисел одночасно, процес стає складнішим - ви повинні визначити, як помилки збиратимуться, тобто що потрібно мінімізувати.
Добре проголосували відповідь на 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 не потребує округлення, одне із значень цього списку потрібно округлювати; найменша відносна помилка є результатом округлення цього великого значення, а не набагато менших альтернатив.
Ця відповідь спочатку виступала за проходження будь-якої можливої комбінації "вгору / вниз", але як зазначається в коментарях, простіший метод працює краще. Алгоритм та код відображають це спрощення.