O objetivo do arredondamento é gerar a menor quantidade de erros. Quando você arredonda um único valor, esse processo é simples e direto, e a maioria das pessoas o entende facilmente. Ao arredondar vários números ao mesmo tempo, o processo fica mais complicado - você deve definir como os erros serão combinados, ou seja, o que deve ser minimizado.
A resposta bem votada de Varun Vohra minimiza a soma dos erros absolutos e é muito simples de implementar. No entanto, existem casos extremos que ele não lida - qual deve ser o resultado do arredondamento 24.25, 23.25, 27.25, 25.25
? Um deles precisa ser arredondado para cima em vez de para baixo. Você provavelmente escolheria arbitrariamente o primeiro ou o último da lista.
Talvez seja melhor usar o erro relativo em vez do absoluto erro . O arredondamento de 23,25 a 24 altera em 3,2%, enquanto o de 27,25 a 28 altera apenas 2,8%. Agora há um vencedor claro.
É possível ajustar isso ainda mais. Uma técnica comum é ajustar cada erro ao quadrado , de modo que os erros maiores sejam desproporcionalmente mais que os pequenos. Eu também usaria um divisor não linear para obter o erro relativo - não parece certo que um erro de 1% seja 99 vezes mais importante do que um erro de 99%. No código abaixo, usei a raiz quadrada.
O algoritmo completo é o seguinte:
- Soma as porcentagens após arredondá-las para baixo e subtrai de 100. Isso indica quantas dessas porcentagens devem ser arredondadas para cima.
- Gere duas pontuações de erro para cada porcentagem, uma quando arredondada para baixo e outra quando arredondada para cima. Pegue a diferença entre os dois.
- Classifique as diferenças de erro produzidas acima.
- Para o número de porcentagens que precisam ser arredondadas, pegue um item da lista classificada e aumente a porcentagem arredondada em 1.
Você ainda pode ter mais de uma combinação com a mesma soma de erros, por exemplo 33.3333333, 33.3333333, 33.3333333
. Isso é inevitável, e o resultado será completamente arbitrário. O código que eu dou abaixo prefere arredondar os valores à esquerda.
Juntar tudo isso em Python se parece com isso.
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]
Como você pode ver no último exemplo, esse algoritmo ainda é capaz de fornecer resultados não intuitivos. Embora 89.0 não precise de arredondamento, um dos valores nessa lista precisava ser arredondado; o menor erro relativo resulta do arredondamento desse valor grande em vez das alternativas muito menores.
Essa resposta originalmente defendia todas as combinações possíveis de arredondamento para cima / baixo, mas, como indicado nos comentários, um método mais simples funciona melhor. O algoritmo e o código refletem essa simplificação.