Eu sei que as perguntas sobre arredondamento em python já foram feitas várias vezes, mas as respostas não me ajudaram. Estou procurando um método que está arredondando um número flutuante pela metade e retorna um número flutuante. O método também deve aceitar um parâmetro que define a casa decimal para a qual arredondar. Eu escrevi um método que implementa esse tipo de arredondamento. No entanto, acho que não parece nada elegante.
def round_half_up(number, dec_places):
s = str(number)
d = decimal.Decimal(s).quantize(
decimal.Decimal(10) ** -dec_places,
rounding=decimal.ROUND_HALF_UP)
return float(d)
Eu não gosto disso, que eu tenho que converter float em uma string (para evitar imprecisão de ponto flutuante) e depois trabalhar com o módulo decimal . Você tem soluções melhores?
Edit: Como apontado nas respostas abaixo, a solução para o meu problema não é tão óbvia quanto o arredondamento correto requer uma representação correta dos números em primeiro lugar, e esse não é o caso do float. Então, eu esperaria que o seguinte código
def round_half_up(number, dec_places):
d = decimal.Decimal(number).quantize(
decimal.Decimal(10) ** -dec_places,
rounding=decimal.ROUND_HALF_UP)
return float(d)
(que difere do código acima apenas pelo fato de que o número flutuador é convertida diretamente em um número decimal e não para uma string primeiro) para retornar 2,18 quando usado como este: round_half_up(2.175, 2)
Mas não porque Decimal(2.175)
vai voltar Decimal('2.17499999999999982236431605997495353221893310546875')
, a forma como o flutuador número é representado pelo computador. Surpreendentemente, o primeiro código retorna 2,18 porque o número flutuante é convertido em string primeiro. Parece que a função str () conduz um arredondamento implícito para o número que inicialmente deveria ser arredondado. Portanto, existem dois arredondamentos. Mesmo que esse seja o resultado que eu esperaria, é tecnicamente errado.
fonte
Respostas:
Surpreendentemente é difícil fazer o arredondamento , porque você precisa lidar com os cálculos de ponto flutuante com muito cuidado. Se você está procurando uma solução elegante (curta, fácil de entender), o que você gosta é um bom ponto de partida. Para estar correto, você deve substituir
decimal.Decimal(str(number))
pela criação do decimal a partir do próprio número, o que fornecerá uma versão decimal de sua representação exata:Decimal(str(number))
efetivamente arredonda duas vezes , pois a formatação do float na representação de sequência executa seu próprio arredondamento. Isso ocorre porquestr(float value)
não tentará imprimir a representação decimal completa do flutuador, ele imprimirá apenas dígitos suficientes para garantir que você recupere o mesmo flutuador se passar esses dígitos exatos para ofloat
construtor.Se você deseja manter o arredondamento correto, mas evite depender do
decimal
módulo grande e complexo , certamente poderá fazê-lo, mas ainda precisará de alguma maneira de implementar a aritmética exata necessária para o arredondamento correto. Por exemplo, você pode usar frações :Observe que a única parte complicada no código acima é a conversão precisa de um ponto flutuante em uma fração e que pode ser descarregada no
as_integer_ratio()
método float, que é o que os decimais e as frações fazem internamente. Portanto, se você realmente deseja remover a dependênciafractions
, pode reduzir a aritmética fracionária para aritmética de número inteiro puro; você permanece na mesma contagem de linhas à custa de alguma legibilidade:Observe que testar essas funções destaca as aproximações realizadas ao criar números de ponto flutuante. Por exemplo,
print(round_half_up(2.175, 2))
imprime2.17
porque o número decimal2.175
não pode ser representado exatamente em binário, portanto, é substituído por uma aproximação que passa a ser um pouco menor que o decimal 2.175. A função recebe esse valor, o acha menor que a fração real correspondente ao decimal 2.175 e decide arredondá-lo para baixo . Isso não é uma peculiaridade da implementação; o comportamento deriva das propriedades dos números de ponto flutuante e também está presente noround
built-in do Python 3 e 2 .fonte
d = Decimal(number).quantize(...)
é que não há representação exata dos números flutuantes. AssimDecimal(2.175)
, você fornecerá decimal ('2.17499999 ...') (e, portanto, o arredondamento estará incorreto) enquantoDecimal('2.175')
estiver decimal ('2.175'). É por isso que estou convertendo para string primeiro.as_integer_ratio()
(no caso de 0,1, que seria 3602879701896397/36028797018963968). Odecimal.Decimal(float)
construtor usa essa representação e o arredondamento subsequente será correto e executado exatamente.round_half_up(2.175, 2)
e ela retornou 2,17, o que está incorreto. Talvez haja algo mais que eu esteja fazendo errado?'%.20f' % 2.175
que avalia'2.17499999999999982236'
.) Por exemplo, no Python 2.7, que usa arredondamento para metade, round (2.175) retorna 2.17, e esse tipo de resultado é documentado como uma limitação do ponto flutuante.round
(que implementa a metade da metade) também não é bom o suficiente. Edite a pergunta para especificar o caso de uso real - talvez ele seja satisfeito evitando flutuações e usando decimais.Sim; use
Decimal
para representar seus números em todo o programa, se você precisar representar exatamente números como 2.675 e tê-los arredondados para 2,68 em vez de 2,67.Não há outro caminho. O número do ponto flutuante que é mostrado na tela como 2.675 não é o número real 2.675; de fato, é um pouco menor que 2.675, razão pela qual é arredondado para 2,67:
Ele é exibido apenas na forma de sequência,
'2.675'
porque é a sequência mais curtafloat(s) == 2.6749999999999998
. Observe que essa representação mais longa (com muitos 9s) também não é exata.Como você escreve sua função de arredondamento, não é possível
my_round(2.675, 2)
arredondar para cima2.68
e também paramy_round(2 + 0.6749999999999998, 2)
arredondar para2.67
; porque as entradas são realmente o mesmo número de ponto flutuante.Portanto, se seu número 2.675 for convertido em um float e vice-versa, você já perdeu as informações sobre se deve arredondar para cima ou para baixo. A solução não é fazê-lo flutuar em primeiro lugar.
fonte
Depois de muito tempo tentando produzir uma função elegante de uma linha, acabei obtendo algo comparável a um dicionário em tamanho.
Eu diria que a maneira mais simples de fazer isso é apenas
eu reconheceria que isso não é preciso em todos os casos, mas deve funcionar se você quiser apenas uma solução rápida e simples.
fonte
round_half_up(1.499999999, 0)
retorna 2,0 em vez de 1,0.