Comportamento de arredondamento do Python 3.x

176

Eu estava apenas relendo o que há de novo no Python 3.0 e ele afirma:

A estratégia de arredondamento da função round () e o tipo de retorno foram alterados. Os casos exatos na metade agora são arredondados para o resultado par mais próximo, em vez de se afastarem de zero. (Por exemplo, a rodada (2,5) agora retorna 2 em vez de 3.)

e a documentação para a rodada :

Para os tipos internos que suportam round (), os valores são arredondados para o múltiplo mais próximo de 10 para a potência menos n; se dois múltiplos estão igualmente próximos, o arredondamento é feito para a escolha uniforme

Portanto, na v2.7.3 :

In [85]: round(2.5)
Out[85]: 3.0

In [86]: round(3.5)
Out[86]: 4.0

como eu esperava. No entanto, agora na v3.2.3 :

In [32]: round(2.5)
Out[32]: 2

In [33]: round(3.5)
Out[33]: 4

Isso parece contra-intuitivo e contrário ao que eu entendo sobre arredondamentos (e com a intenção de enganar as pessoas). O inglês não é minha língua nativa, mas até ler isso, pensei que sabia o que significava o arredondamento: - / Tenho certeza de que, na época em que a v3 foi introduzida, devia haver alguma discussão sobre isso, mas não consegui encontrar uma boa razão para isso. minha pesquisa.

  1. Alguém tem uma idéia de por que isso foi alterado para isso?
  2. Existem outras linguagens de programação convencionais (por exemplo, C, C ++, Java, Perl, ..) que fazem esse tipo de arredondamento (para mim inconsistente)?

O que estou perdendo aqui?

UPDATE: O comentário de @ Li-aungYip sobre "Arredondamento de banqueiro" me deu o termo de pesquisa / palavras-chave corretas para pesquisar e encontrei a seguinte pergunta: Por que o .NET usa o arredondamento de banqueiros como padrão? , então eu vou ler isso com atenção.

Levon
fonte
27
Não tenho tempo para procurar isso, mas acredito que isso se chama "arredondamento dos banqueiros". Eu acredito que é comum no setor financeiro.
Li-aung Yip
2
@berry bem, sim, seu comportamento é consistente com sua própria descrição. Portanto, se disser que "arredondamento" está dobrando seu valor e o fizer, também seria consistente :) ... mas parece contrário ao que o arredondamento geralmente significa . Então, eu estou procurando uma melhor compreensão.
Levon
1
@ Li-aungYip Obrigado pela liderança re "Arredondamento do banqueiro" .. Vou procurá-lo.
Levon
1
Veja também: stackoverflow.com/questions/10093783/…
Sven Marnach
3
Apenas uma observação: o arredondamento dos banqueiros não é comum apenas nas finanças. Isto é como eu fui ensinado a rodada na escola primária já na década de 70 :-)
Lennart Regebro

Respostas:

160

A maneira do Python 3.0 é considerada o método de arredondamento padrão atualmente, embora algumas implementações de linguagem ainda não estejam no barramento.

A técnica simples "sempre arredondar 0,5 para cima" resulta em um leve viés em direção ao número mais alto. Com um grande número de cálculos, isso pode ser significativo. A abordagem do Python 3.0 elimina esse problema.

Há mais de um método de arredondamento em uso comum. O IEEE 754, o padrão internacional para matemática de ponto flutuante, define cinco métodos de arredondamento diferentes (o usado pelo Python 3.0 é o padrão). E há outros.

Esse comportamento não é tão amplamente conhecido como deveria ser. O AppleScript foi, se bem me lembro, um dos primeiros a adotar esse método de arredondamento. O roundcomando no AppleScript realmente oferece várias opções, mas o arredondamento para o par é o padrão, como é o IEEE 754. Aparentemente, o engenheiro que implementou o roundcomando ficou tão cansado de todas as solicitações para "fazê-lo funcionar como eu aprendi em" escola "que ele implementou exatamente isso: round 2.5 rounding as taught in schoolé um comando AppleScript válido. :-)

kindall
fonte
4
Não conhecia esse "método de arredondamento padrão padrão universalmente hoje em dia", você (ou qualquer outra pessoa) saberia se C / C ++ / Java / Perl ou qualquer outra linguagem "main-stream" implementa o arredondamento da mesma maneira?
Levon
3
Ruby faz isso. As linguagens .NET da Microsoft fazem isso. Java não parece, no entanto. Não consigo identificá-lo para todos os idiomas possíveis, mas acho que é mais comum em idiomas projetados recentemente. Eu imagino que C e C ++ tenham idade suficiente para que não.
Kindall
5
ruby retorna 3para2.5.round
jfs
14
Eu adicionei um pouco sobre como o AppleScript lidou com isso, porque eu amo a maneira sarcástica em que o comportamento "antigo" é implementado.
Kindall
2
@kindall Esse método é o modo de arredondamento padrão do IEEE desde 1985 (quando o IEEE 754-1985 foi publicado). Também tem sido o modo de arredondamento padrão em C desde pelo menos C89 (e, portanto, também em C ++), no entanto , desde C99 (e C ++ 11 com suporte esporádico antes disso) uma função "round ()" está disponível e usa laços em volta do zero. O arredondamento interno do ponto flutuante e a família de funções rint () ainda obedecem à configuração do modo de arredondamento, que padroniza os vínculos de arredondamento para par.
Wlerin
41

Você pode controlar o arredondamento obtido no Py3000 usando o módulo Decimal :

>>> decimal.Decimal('3.5').quantize(decimal.Decimal('1'), 
    rounding=decimal.ROUND_HALF_UP)
>>> Decimal('4')

>>> decimal.Decimal('2.5').quantize(decimal.Decimal('1'),    
    rounding=decimal.ROUND_HALF_EVEN)
>>> Decimal('2')

>>> decimal.Decimal('3.5').quantize(decimal.Decimal('1'), 
    rounding=decimal.ROUND_HALF_DOWN)
>>> Decimal('3')
dawg
fonte
Obrigado .. Eu não estava familiarizado com este módulo. Alguma idéia de como eu obteria o comportamento do Python v 2.x? Os exemplos que você mostra não parecem fazer isso. Apenas curioso se isso seria possível.
Levon 31/05
1
@ Levon: A constante ROUND_HALF_UPé igual ao antigo comportamento do Python 2.X.
Dawg
2
Você também pode definir um contexto para o módulo Decimal que faça isso implicitamente. Veja a setcontext()função.
kindall 31/05
Era exatamente isso que eu estava procurando hoje. Trabalhando como esperado no Python 3.4.3. Também é importante notar que você pode controlar quanto arredondará alterando quantize(decimal.Decimal('1')para quantize(decimal.Decimal('0.00')se deseja arredondar para os 100 mais próximos, como por dinheiro.
Igor
Essa solução funciona como substituta pelo round(number, ndigits)tempo que ndigitsfor positivo, mas irritantemente você não pode usá-la para substituir algo assim round(5, -1).
Pekka Klärck 15/06/19
15

Apenas para adicionar aqui uma observação importante da documentação:

https://docs.python.org/dev/library/functions.html#round

Nota

O comportamento de round () para carros alegóricos pode ser surpreendente: por exemplo, round (2.675, 2) fornece 2,67 em vez dos 2,68 esperados. Isso não é um bug: é resultado do fato de que a maioria das frações decimais não pode ser representada exatamente como um float. Consulte Aritmética de ponto flutuante: problemas e limitações para obter mais informações.

Portanto, não se surpreenda ao obter os seguintes resultados no Python 3.2:

>>> round(0.25,1), round(0.35,1), round(0.45,1), round(0.55,1)
(0.2, 0.3, 0.5, 0.6)

>>> round(0.025,2), round(0.035,2), round(0.045,2), round(0.055,2)
(0.03, 0.04, 0.04, 0.06)
skif1979
fonte
Eu vi isso. E minha primeira reação: quem está usando uma CPU de 16 bits que é incapaz de representar todas as permutações de "2,67x"? Dizendo que frações não pode ser expresso em flutuador parece ser um bode expiatório aqui: sem CPU moderna é que imprecisas, em qualquer langauge (exceto Python?)
Adam
9
@ Adam: Eu acho que você está entendendo mal. O formato binário (IEEE 754 binary64) usado para armazenar carros alegóricos não pode representar 2.675exatamente: o mais próximo possível do computador 2.67499999999999982236431605997495353221893310546875. Isso é bem próximo, mas não é exatamente igual a 2.675: é um pouco mais próximo 2.67do que 2.68. Portanto, a roundfunção faz a coisa certa e a arredonda para o valor mais próximo de 2 dígitos após o ponto, a saber 2.67. Isso não tem nada a ver com Python, e tudo a ver com ponto flutuante binário.
Mark Dickinson
3
Não é "a coisa certa" porque recebeu uma constante de código-fonte :), mas entendo o seu ponto.
Adam
@ Adam: Eu encontrei essa mesma peculiaridade em JS antes, então não é específico ao idioma.
Igor
5

Recentemente, também tive problemas com isso. Por isso, desenvolvi um módulo python 3 que possui 2 funções trueround () e trueround_precision () que abordam isso e dão o mesmo comportamento de arredondamento usado na escola primária (não o arredondamento de banqueiros). Aqui está o módulo. Apenas salve o código e copie-o ou importe-o. Nota: o módulo trueround_precision pode alterar o comportamento do arredondamento, dependendo das necessidades, de acordo com os sinalizadores ROUND_CEILING, ROUND_DOWN, ROUND_FLOOR, ROUND_HALF_DOWN, ROUND_HALF_EVEN, ROUND_HALF_UP, ROUND_UP e ROUND_05UP no módulo decimal (consulte a documentação desses módulos para obter mais informações). Para as funções abaixo, consulte as instruções ou use help (trusteound) e help (trueround_precision) se copiado em um intérprete para obter mais documentação.

#! /usr/bin/env python3
# -*- coding: utf-8 -*-

def trueround(number, places=0):
    '''
    trueround(number, places)

    example:

        >>> trueround(2.55, 1) == 2.6
        True

    uses standard functions with no import to give "normal" behavior to 
    rounding so that trueround(2.5) == 3, trueround(3.5) == 4, 
    trueround(4.5) == 5, etc. Use with caution, however. This still has 
    the same problem with floating point math. The return object will 
    be type int if places=0 or a float if places=>1.

    number is the floating point number needed rounding

    places is the number of decimal places to round to with '0' as the
        default which will actually return our interger. Otherwise, a
        floating point will be returned to the given decimal place.

    Note:   Use trueround_precision() if true precision with
            floats is needed

    GPL 2.0
    copywrite by Narnie Harshoe <[email protected]>
    '''
    place = 10**(places)
    rounded = (int(number*place + 0.5if number>=0 else -0.5))/place
    if rounded == int(rounded):
        rounded = int(rounded)
    return rounded

def trueround_precision(number, places=0, rounding=None):
    '''
    trueround_precision(number, places, rounding=ROUND_HALF_UP)

    Uses true precision for floating numbers using the 'decimal' module in
    python and assumes the module has already been imported before calling
    this function. The return object is of type Decimal.

    All rounding options are available from the decimal module including 
    ROUND_CEILING, ROUND_DOWN, ROUND_FLOOR, ROUND_HALF_DOWN, ROUND_HALF_EVEN, 
    ROUND_HALF_UP, ROUND_UP, and ROUND_05UP.

    examples:

        >>> trueround(2.5, 0) == Decimal('3')
        True
        >>> trueround(2.5, 0, ROUND_DOWN) == Decimal('2')
        True

    number is a floating point number or a string type containing a number on 
        on which to be acted.

    places is the number of decimal places to round to with '0' as the default.

    Note:   if type float is passed as the first argument to the function, it
            will first be converted to a str type for correct rounding.

    GPL 2.0
    copywrite by Narnie Harshoe <[email protected]>
    '''
    from decimal import Decimal as dec
    from decimal import ROUND_HALF_UP
    from decimal import ROUND_CEILING
    from decimal import ROUND_DOWN
    from decimal import ROUND_FLOOR
    from decimal import ROUND_HALF_DOWN
    from decimal import ROUND_HALF_EVEN
    from decimal import ROUND_UP
    from decimal import ROUND_05UP

    if type(number) == type(float()):
        number = str(number)
    if rounding == None:
        rounding = ROUND_HALF_UP
    place = '1.'
    for i in range(places):
        place = ''.join([place, '0'])
    return dec(number).quantize(dec(place), rounding=rounding)

Espero que isto ajude,

Narnie

narnie
fonte
5

O Python 3.x arredonda 0,5 valores para um vizinho que é

assert round(0.5) == 0
assert round(1.5) == 2
assert round(2.5) == 2

import decimal

assert decimal.Decimal('0.5').to_integral_value() == 0
assert decimal.Decimal('1.5').to_integral_value() == 2
assert decimal.Decimal('2.5').to_integral_value() == 2

no entanto, pode-se alterar o arredondamento decimal "voltar" para sempre arredondar 0,5, se necessário:

decimal.getcontext().rounding = decimal.ROUND_HALF_UP

assert decimal.Decimal('0.5').to_integral_value() == 1
assert decimal.Decimal('1.5').to_integral_value() == 2
assert decimal.Decimal('2.5').to_integral_value() == 3

i = int(decimal.Decimal('2.5').to_integral_value()) # to get an int
assert i == 3
assert type(i) is int
kares
fonte
1

Comportamento de arredondamento do Python 2 no python 3.

Adicionando 1 nas 15 casas decimais. Precisão de até 15 dígitos.

round2=lambda x,y=None: round(x+1e-15,y)
SmartManoj
fonte
3
Você poderia explicar a intuição por trás dessa fórmula?
Hadi
2
Pelo que entendi, frações que não podem ser representadas com precisão terão até 15 9, depois a imprecisão. Por exemplo, 2.675é 2.67499999999999982236431605997495353221893310546875. A adição de 1e-15 fará com que caia mais de 2.675 e a arredonde corretamente. se a fração já estiver acima da constante do código, adicionar 1e-15 não mudará nada no arredondamento.
Benoit Dufresne
1

Alguns casos:

in: Decimal(75.29 / 2).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
in: round(75.29 / 2, 2)
out: 37.65 GOOD

in: Decimal(85.55 / 2).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
in: round(85.55 / 2, 2)
out: 42.77 BAD

Para correção:

in: round(75.29 / 2 + 0.00001, 2)
out: 37.65 GOOD
in: round(85.55 / 2 + 0.00001, 2)
out: 42.78 GOOD

Se você quiser mais casas decimais, por exemplo 4, adicione (+ 0,0000001).

Trabalhe para mim.

Virako
fonte
Esta foi a única solução que funcionou para mim, obrigado por postar. Todo mundo parece ter a intenção de arredondar 0,5 para cima / para baixo, então não pude gerenciar problemas de arredondamento com várias casas decimais.
precisa
-1

Reprodução de amostra:

['{} => {}'.format(x+0.5, round(x+0.5)) for x in range(10)]

['0.5 => 0', '1.5 => 2', '2.5 => 2', '3.5 => 4', '4.5 => 4', '5.5 => 6', '6.5 => 6', '7.5 => 8', '8.5 => 8', '9.5 => 10']

API: https://docs.python.org/3/library/functions.html#round

Estados:

Retornar número arredondado para precisão ndigits após o ponto decimal. Se ndigits for omitido ou for None, ele retornará o número inteiro mais próximo de sua entrada.

Para os tipos internos que suportam round (), os valores são arredondados para o múltiplo mais próximo de 10 para a potência menos ndigits; se dois múltiplos são igualmente próximos, o arredondamento é feito para a opção par (por exemplo, tanto o arredondamento (0,5) quanto o arredondado (-0,5) são 0 e o arredondamento (1,5) é 2). Qualquer valor inteiro é válido para ndigits (positivo, zero ou negativo). O valor de retorno é um número inteiro se ndigits for omitido ou Nenhum. Caso contrário, o valor retornado tem o mesmo tipo que número.

Para um número de objeto Python geral, arredonde delegados para number. rodada .

Nota O comportamento de round () para carros alegóricos pode ser surpreendente: por exemplo, round (2.675, 2) fornece 2,67 em vez dos 2,68 esperados. Isso não é um bug: é resultado do fato de que a maioria das frações decimais não pode ser representada exatamente como um float. Consulte Aritmética de ponto flutuante: problemas e limitações para obter mais informações.

Dada essa percepção, você pode usar um pouco de matemática para resolvê-la

import math
def my_round(i):
  f = math.floor(i)
  return f if i - f < 0.5 else f+1

agora você pode executar o mesmo teste com my_round em vez de round.

['{} => {}'.format(x + 0.5, my_round(x+0.5)) for x in range(10)]
['0.5 => 1', '1.5 => 2', '2.5 => 3', '3.5 => 4', '4.5 => 5', '5.5 => 6', '6.5 => 7', '7.5 => 8', '8.5 => 9', '9.5 => 10']
Fallenreaper
fonte
-2

A maneira mais fácil de arredondar no Python 3.x, como ensinado na escola, é usar uma variável auxiliar:

n = 0.1 
round(2.5 + n)

E estes serão os resultados das séries 2.0 a 3.0 (em 0.1 etapas):

>>> round(2 + n)
>>> 2

>>> round(2.1 + n)
>>> 2

>>> round(2.2 + n)
>>> 2

>>> round(2.3 + n)
>>> 2

>>> round(2.4 + n)
>>> 2

>>> round(2.5 + n)
>>> 3

>>> round(2.6 + n)
>>> 3

>>> round(2.7 + n)
>>> 3

>>> round(2.8 + n)
>>> 3

>>> round(2.9 + n)
>>> 3

>>> round(3 + n)
>>> 3
baermathias
fonte
-2

Você pode controlar o arredondamento usando o módulo math.ceil:

import math
print(math.ceil(2.5))
> 3
Eds_k
fonte
Isso sempre retornará o número sem sua parte decimal, isso não é arredondamento. ceil (2.5) = 2, ceil (2.99) = 2
krafter 17/02
1
em python3 +, se o argumento numérico for positivo ou negativo, a função ceil retornará o valor do teto.
Eds_k 17/02
In [14]: math.ceil (2.99) Out [14]: 3
Eds_k
Sim, me desculpe, eu estava errado. Ceil () retorna o valor do teto, enquanto floor () retorna o que eu estava falando. Mas ainda assim, na minha opinião, esse não é exatamente o comportamento de arredondamento (ambas as funções)
krafter 19/02
-4

Tente este código:

def roundup(input):   
   demo = input  if str(input)[-1] != "5" else str(input).replace("5","6")
   place = len(demo.split(".")[1])-1
   return(round(float(demo),place))

O resultado será:

>>> x = roundup(2.5)
>>> x
3.0  
>>> x = roundup(2.05)
>>> x
2.1 
>>> x = roundup(2.005)
>>> x
2.01 

Saída você pode conferir aqui: https://i.stack.imgur.com/QQUkS.png

Anil Kumar Sahoo
fonte