round () não parece estar arredondando corretamente


123

A documentação da função round () indica que você passa um número para ele e as posições passam do decimal para o arredondamento. Assim, deve fazer o seguinte:

n = 5.59
round(n, 1) # 5.6

Mas, na verdade, a boa e velha estranheza de ponto flutuante aparece e você obtém:

5.5999999999999996

Para os fins da interface do usuário, eu preciso exibir 5.6. Procurei na Internet e encontrei alguma documentação que depende da minha implementação do Python. Infelizmente, isso ocorre na minha máquina Windows dev e em cada servidor Linux que eu tentei. Veja aqui também .

Com exceção de criar minha própria biblioteca redonda, existe alguma maneira de contornar isso?


4
Eu tentei isso com python 2.7.11 rodada (5,59) e ele está dando resultado como 5.6 em Windows e Linux x86 máquina de 64 bits, Cython (o link documentação mencionada é mudado agora eu acho)?
Alex Punnen

2
Onde realmente não funciona corretamente é round(5.55, 1) = 5.5.
Dmitry

Respostas:


101

Não posso ajudar na maneira como ele é armazenado, mas pelo menos a formatação funciona corretamente:

'%.1f' % round(n, 1) # Gives you '5.6'

11
Eu tentei print '%.2f' % 655.665, mas ele retorna 655.66, deve ser655.67
Liza

1
@Kyrie, veja stackoverflow.com/questions/9301690/… . A imprecisão do ponto flutuante é a culpa aqui - "5.665 -> 5.67", mas "15.665 -> 15.66". Use decimais se precisar de precisão exata.
21715 Jimmy

7
isso está funcionando após busca :) from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_DOWN# uso no arredondamento números flutuantes Decimal(str(655.665)).quantize(Decimal('1.11'), rounding=ROUND_HALF_UP)# problemas e limitações em pontos flutuantes
Liza

102

A formatação funciona corretamente, mesmo sem a necessidade de arredondar:

"%.1f" % n

18
De acordo com os documentos , esse estilo de formatação de string acabará desaparecendo. O formato de novo estilo seria"{:.1f}".format(n)
whereswalden 7/08/14

2
Não arredonda corretamente: '%.5f' % 0.9886250.98862
schlamar

@schlamar: Esse também é o comportamento de round (): round (0.988625,5) também fornece 0.98862. round (0.988626,5), bem como "% .5f" % 0,988626 dar 0,98863
Vinko Vrsalovic

infelizmente "% .2f"% 2.675 retornará 2,67 - o que pode ser uma resposta inesperada para quem usa esse método e espera 2,68
Dion

30

Se você usar o módulo Decimal, poderá aproximar-se sem o uso da função 'round'. Aqui está o que eu tenho usado para arredondar, especialmente ao escrever aplicativos monetários:

Decimal(str(16.2)).quantize(Decimal('.01'), rounding=ROUND_UP)

Isso retornará um número decimal que é 16,20.


4
Esta é a resposta canônica - onde a precisão importa, de qualquer maneira, que está praticamente em todo lugar. Claro: é um pouco detalhado . Mas jogue esse otário em uma função auxiliar e você estará pronto para formatar e partir.
Cecil Curry

2
rounding='ROUND_UP'
LMC

Se você receber esse erro NameError: global name 'ROUND_UP' is not definedvocê precisa importar a sua função de arredondamento: from decimal import Decimal, ROUND_UP. Outras funções de arredondamento
Stephen Blair

Seu exemplo ainda parece perigoso: você conta com o arredondamento fornecido por str ().
YvesgereY 01/10/19

21

round(5.59, 1)está funcionando bem. O problema é que o 5.6 não pode ser representado exatamente no ponto flutuante binário.

>>> 5.6
5.5999999999999996
>>> 

Como Vinko diz, você pode usar a formatação de seqüência de caracteres para arredondar para exibição.

Python possui um módulo para aritmética decimal, se você precisar.


1
Isto não é l mais um problema com o Python 2.7 ou Python 3.5
vy32


10

Você pode alternar o tipo de dados para um número inteiro:

>>> n = 5.59
>>> int(n * 10) / 10.0
5.5
>>> int(n * 10 + 0.5)
56

E, em seguida, exiba o número inserindo o separador decimal da localidade.

No entanto, a resposta de Jimmy é melhor.


5

A matemática de ponto flutuante é vulnerável a imprecisões leves, mas irritantes, de precisão. Se você pode trabalhar com um número inteiro ou ponto fixo, você terá precisão garantida.


5

Dê uma olhada no módulo Decimal

Decimal "é baseado em um modelo de ponto flutuante que foi projetado com as pessoas em mente e necessariamente tem um princípio orientador primordial - os computadores devem fornecer uma aritmética que funcione da mesma maneira que a aritmética que as pessoas aprendem na escola". - trecho da especificação aritmética decimal.

e

Os números decimais podem ser representados exatamente. Por outro lado, números como 1.1 e 2.2 não têm representações exatas no ponto flutuante binário. Os usuários finais normalmente não esperam que 1.1 + 2.2 seja exibido como 3.3000000000000003, como acontece com o ponto flutuante binário.

Decimal fornece o tipo de operações que facilitam a gravação de aplicativos que exigem operações de ponto flutuante e também precisam apresentar esses resultados em um formato legível por humanos, por exemplo, contabilidade.



4

É realmente um grande problema. Experimente este código:

print "%.2f" % (round((2*4.4+3*5.6+3*4.4)/8,2),)

Exibe 4,85. Então você faz:

print "Media = %.1f" % (round((2*4.4+3*5.6+3*4.4)/8,1),)

e mostra 4.8. Você calcula manualmente a resposta exata é 4,85, mas se tentar:

print "Media = %.20f" % (round((2*4.4+3*5.6+3*4.4)/8,20),)

você pode ver a verdade: o ponto flutuante é armazenado como a soma finita mais próxima de frações cujos denominadores são potências de dois.


3

Você pode usar o operador de formato de sequência %, semelhante ao sprintf.

mystring = "%.2f" % 5.5999


2

Estou fazendo:

int(round( x , 0))

Nesse caso, primeiro arredondamos corretamente no nível da unidade e depois convertemos para número inteiro para evitar a impressão de um flutuador.

tão

>>> int(round(5.59,0))
6

Acho que essa resposta funciona melhor do que formatar a string e também faz mais sentido usar a função round.


2

Eu evitaria confiar round()nesse caso. Considerar

print(round(61.295, 2))
print(round(1.295, 2))

irá produzir

61.3
1.29

que não é uma saída desejada se você precisar de arredondamentos sólidos para o número inteiro mais próximo. Para ignorar esse comportamento, vá com math.ceil()(ou math.floor()se você deseja arredondar para baixo):

from math import ceil
decimal_count = 2
print(ceil(61.295 * 10 ** decimal_count) / 10 ** decimal_count)
print(ceil(1.295 * 10 ** decimal_count) / 10 ** decimal_count)

saídas

61.3
1.3

Espero que ajude.


1

Código:

x1 = 5.63
x2 = 5.65
print(float('%.2f' % round(x1,1)))  # gives you '5.6'
print(float('%.2f' % round(x2,1)))  # gives you '5.7'

Resultado:

5.6
5.7

0

Aqui é onde vejo a rodada falhando. E se você quisesse arredondar esses 2 números para uma casa decimal? 23,45 23,55 Minha educação foi que, ao arredondar esses números, você deve obter: 23,4 23,6 a "regra" é que você deve arredondar para cima se o número anterior for ímpar, não arredondar para cima se o número anterior for par. A função round em python simplesmente trunca o 5.


1
O que você está falando é sobre o "arredondamento dos banqueiros" , uma das muitas maneiras diferentes de realizar o arredondamento.
Simon MᶜKenzie

0

O problema é apenas quando o último dígito é 5. Por exemplo. 0,045 é armazenado internamente como 0,044999999999999 ... Você pode simplesmente aumentar o último dígito para 6 e arredondar. Isso lhe dará os resultados desejados.

import re


def custom_round(num, precision=0):
    # Get the type of given number
    type_num = type(num)
    # If the given type is not a valid number type, raise TypeError
    if type_num not in [int, float, Decimal]:
        raise TypeError("type {} doesn't define __round__ method".format(type_num.__name__))
    # If passed number is int, there is no rounding off.
    if type_num == int:
        return num
    # Convert number to string.
    str_num = str(num).lower()
    # We will remove negative context from the number and add it back in the end
    negative_number = False
    if num < 0:
        negative_number = True
        str_num = str_num[1:]
    # If number is in format 1e-12 or 2e+13, we have to convert it to
    # to a string in standard decimal notation.
    if 'e-' in str_num:
        # For 1.23e-7, e_power = 7
        e_power = int(re.findall('e-[0-9]+', str_num)[0][2:])
        # For 1.23e-7, number = 123
        number = ''.join(str_num.split('e-')[0].split('.'))
        zeros = ''
        # Number of zeros = e_power - 1 = 6
        for i in range(e_power - 1):
            zeros = zeros + '0'
        # Scientific notation 1.23e-7 in regular decimal = 0.000000123
        str_num = '0.' + zeros + number
    if 'e+' in str_num:
        # For 1.23e+7, e_power = 7
        e_power = int(re.findall('e\+[0-9]+', str_num)[0][2:])
        # For 1.23e+7, number_characteristic = 1
        # characteristic is number left of decimal point.
        number_characteristic = str_num.split('e+')[0].split('.')[0]
        # For 1.23e+7, number_mantissa = 23
        # mantissa is number right of decimal point.
        number_mantissa = str_num.split('e+')[0].split('.')[1]
        # For 1.23e+7, number = 123
        number = number_characteristic + number_mantissa
        zeros = ''
        # Eg: for this condition = 1.23e+7
        if e_power >= len(number_mantissa):
            # Number of zeros = e_power - mantissa length = 5
            for i in range(e_power - len(number_mantissa)):
                zeros = zeros + '0'
            # Scientific notation 1.23e+7 in regular decimal = 12300000.0
            str_num = number + zeros + '.0'
        # Eg: for this condition = 1.23e+1
        if e_power < len(number_mantissa):
            # In this case, we only need to shift the decimal e_power digits to the right
            # So we just copy the digits from mantissa to characteristic and then remove
            # them from mantissa.
            for i in range(e_power):
                number_characteristic = number_characteristic + number_mantissa[i]
            number_mantissa = number_mantissa[i:]
            # Scientific notation 1.23e+1 in regular decimal = 12.3
            str_num = number_characteristic + '.' + number_mantissa
    # characteristic is number left of decimal point.
    characteristic_part = str_num.split('.')[0]
    # mantissa is number right of decimal point.
    mantissa_part = str_num.split('.')[1]
    # If number is supposed to be rounded to whole number,
    # check first decimal digit. If more than 5, return
    # characteristic + 1 else return characteristic
    if precision == 0:
        if mantissa_part and int(mantissa_part[0]) >= 5:
            return type_num(int(characteristic_part) + 1)
        return type_num(characteristic_part)
    # Get the precision of the given number.
    num_precision = len(mantissa_part)
    # Rounding off is done only if number precision is
    # greater than requested precision
    if num_precision <= precision:
        return num
    # Replace the last '5' with 6 so that rounding off returns desired results
    if str_num[-1] == '5':
        str_num = re.sub('5$', '6', str_num)
    result = round(type_num(str_num), precision)
    # If the number was negative, add negative context back
    if negative_number:
        result = result * -1
    return result

0

Outra opção potencial é:

def hard_round(number, decimal_places=0):
    """
    Function:
    - Rounds a float value to a specified number of decimal places
    - Fixes issues with floating point binary approximation rounding in python
    Requires:
    - `number`:
        - Type: int|float
        - What: The number to round
    Optional:
    - `decimal_places`:
        - Type: int 
        - What: The number of decimal places to round to
        - Default: 0
    Example:
    ```
    hard_round(5.6,1)
    ```
    """
    return int(number*(10**decimal_places)+0.5)/(10**decimal_places)

-4

A respeito:

round(n,1)+epsilon

Isso só funcionaria se o arredondamento fosse consistentemente diferente do número da rodada por epsilon. Se epsilon = .000001então round(1.0/5.0, 1) + epsilonpegaria a representação precisa 0.2 e a tornaria 0.00001. Problemas igualmente ruins aconteceriam se o epsilon estivesse dentro da função round.
Michael Scott Cuthbert
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.