Celem zaokrąglania jest wygenerowanie najmniejszej ilości błędów. Gdy zaokrąglasz jedną wartość, proces ten jest prosty i bezpośredni, a większość ludzi łatwo to rozumie. Gdy zaokrąglasz wiele liczb w tym samym czasie, proces staje się trudniejszy - musisz zdefiniować sposób łączenia błędów, tj. Co należy zminimalizować.
Dobrze głosowało odpowiedź przez Varun Vohrą minimalizuje sumę błędów bezwzględnych, i to jest bardzo proste do wykonania. Są jednak przypadki brzegowe, których nie obsługuje - co powinno wynikać z zaokrąglania 24.25, 23.25, 27.25, 25.25
? Jeden z nich należy zaokrąglić w górę zamiast w dół. Prawdopodobnie wybrałbyś pierwszy lub ostatni z listy.
Być może lepiej jest użyć błędu względnego zamiast bezwzględnego błędu . Zaokrąglenie do 23,25 do 24 powoduje zmianę o 3,2%, natomiast zaokrąglenie do 27,25 do 28 zmienia tylko o 2,8%. Teraz jest wyraźny zwycięzca.
Można to jeszcze bardziej ulepszyć. Jedną z powszechnych technik jest wyrównywanie każdego błędu, dzięki czemu duże błędy liczą się nieproporcjonalnie więcej niż małe. Użyłbym również nieliniowego dzielnika, aby uzyskać błąd względny - nie wydaje się właściwe, aby błąd przy 1% był 99 razy ważniejszy niż błąd przy 99%. W poniższym kodzie użyłem pierwiastka kwadratowego.
Kompletny algorytm wygląda następująco:
- Zsumuj wartości procentowe po zaokrągleniu ich wszystkich w dół i odejmij od 100. To pokazuje, ile z tych wartości procentowych należy zaokrąglić w górę.
- Wygeneruj dwa wyniki błędu dla każdego procentu, jeden po zaokrągleniu w dół i jeden po zaokrągleniu w górę. Weź różnicę między nimi.
- Posortuj różnice błędów powstałe powyżej.
- Aby uzyskać liczbę procentową, którą należy zaokrąglić w górę, weź element z posortowanej listy i zwiększ zaokrągloną wartość procentową o 1.
Na przykład nadal możesz mieć więcej niż jedną kombinację z tą samą sumą błędów 33.3333333, 33.3333333, 33.3333333
. Jest to nieuniknione, a wynik będzie całkowicie arbitralny. Podany poniżej kod woli zaokrąglać w górę wartości po lewej stronie.
Złożenie tego wszystkiego razem w Pythonie wygląda następująco.
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]
Jak widać na ostatnim przykładzie, algorytm ten nadal może dostarczać nieintuicyjne wyniki. Mimo że 89,0 nie wymaga zaokrąglania, jedną z wartości z tej listy trzeba zaokrąglić w górę; najniższy błąd względny wynika z zaokrąglenia w górę tej dużej wartości, a nie ze znacznie mniejszych alternatyw.
Ta odpowiedź początkowo opowiadała się za każdą możliwą kombinacją zaokrąglania w górę / zaokrąglania w dół, ale jak wskazano w komentarzach, prostsza metoda działa lepiej. Algorytm i kod odzwierciedlają to uproszczenie.