Limitando flutuadores a duas casas decimais


1691

Eu quero aser arredondado para 13,95 .

>>> a
13.949999999999999
>>> round(a, 2)
13.949999999999999

A roundfunção não funciona da maneira que eu esperava.



6
Hmm ... Você está tentando representar moeda? Nesse caso, você não deve usar carros alegóricos por dólares. Você provavelmente poderia usar flutuadores por centavos, ou qualquer que seja a menor unidade comum de moeda que você está tentando modelar, mas a melhor prática é usar uma representação decimal, como sugeriu HUAGHAGUAH em sua resposta.
SingleNegationElimination

63
É importante não representar moeda no float. Os flutuadores não são precisos. Mas montantes em centavos ou centavos são inteiros. Portanto, os números inteiros são a maneira correta de representar a moeda.
Davoud Taghawi-Nejad 15/07/12

2
@ DavoudTaghawi-Nejad ou mais para o ponto ... O tipo decimal
Básico

17
Estou chegando provavelmente muito tarde aqui, mas eu queria perguntar: os desenvolvedores do Python resolveram esse problema? Porque quando eu faço a rodada (13.949999999999999, 2), eu simplesmente recebo 13.95. Eu tentei no Python 2.7.6, bem como 3.4. Funciona. Não tenho certeza se 2.7 estava lá em 2009. Talvez seja uma coisa do Python 2.5?
bad_keypoints 22/09/2015

Respostas:


1690

Você está enfrentando o problema antigo com números de ponto flutuante que nem todos os números podem ser representados exatamente. A linha de comando está apenas mostrando o formulário completo de ponto flutuante da memória.

Com a representação de ponto flutuante, sua versão arredondada é o mesmo número. Como os computadores são binários, eles armazenam números de ponto flutuante como um número inteiro e o dividem por uma potência de dois, para que 13,95 sejam representados de maneira semelhante a 125650429603636838 / (2 ** 53).

Os números de precisão dupla têm 53 bits (16 dígitos) de precisão e os flutuadores regulares têm 24 bits (8 dígitos) de precisão. O tipo de ponto flutuante no Python usa precisão dupla para armazenar os valores.

Por exemplo,

>>> 125650429603636838/(2**53)
13.949999999999999

>>> 234042163/(2**24)
13.949999988079071

>>> a = 13.946
>>> print(a)
13.946
>>> print("%.2f" % a)
13.95
>>> round(a,2)
13.949999999999999
>>> print("%.2f" % round(a, 2))
13.95
>>> print("{:.2f}".format(a))
13.95
>>> print("{:.2f}".format(round(a, 2)))
13.95
>>> print("{:.15f}".format(round(a, 2)))
13.949999999999999

Se você tiver apenas duas casas decimais (para exibir um valor da moeda, por exemplo), terá algumas opções melhores:

  1. Use números inteiros e armazene valores em centavos, não em dólares e depois divida por 100 para converter em dólares.
  2. Ou use um número de ponto fixo como decimal .

27
@ Christian Há uma diferença fundamental entre o valor armazenado e como você exibe esse valor. Formatando a saída deve permitir que você adicionar preenchimento conforme necessário, bem como a adição de separadores de vírgula, etc.
Básico

22
vale a pena mencionar que "%.2f" % round(a,2)você pode colocar em não só no printf, mas também em tais coisas comostr()
andilabs

20
por que as pessoas sempre assumem moeda no arredondamento de ponto flutuante? às vezes você só quer trabalhar com menos precisão.
WORC

9
@radtek: Você precisa entender que o valor binário (do tipo float) é apenas a aproximação mais próxima disponível do número decimal (que você conhece como ser humano). Não existe um valor binário (finitamente representável) como 0,245. Simplesmente não existe e matematicamente não pode existir. O valor binário mais próximo de 0,245 é um pouco menor que 0,245; portanto, naturalmente é arredondado para baixo. Da mesma forma, não existe 0,225 em binário, mas o valor binário mais próximo de 0,225 é um pouco maior que 0,225, então, naturalmente, ele é arredondado.
John Y

12
@radtek: Você pediu literalmente uma explicação. A solução mais direta é realmente usar Decimal, e essa foi uma das soluções apresentadas nesta resposta. A outra era converter suas quantidades em número inteiro e usar aritmética de número inteiro. Ambas as abordagens também apareceram em outras respostas e comentários.
John Y

586

Existem novas especificações de formato, mini-idioma de especificação de formato de string :

Você pode fazer o mesmo que:

"{:.2f}".format(13.949999999999999)

Nota 1: o exemplo acima retorna uma string. Para obter o float, basta enrolar com float(...):

float("{:.2f}".format(13.949999999999999))

Nota 2: embrulhar com float()não muda nada:

>>> x = 13.949999999999999999
>>> x
13.95
>>> g = float("{:.2f}".format(x))
>>> g
13.95
>>> x == g
True
>>> h = round(x, 2)
>>> h
13.95
>>> x == h
True

17
para adicionar vírgulas, você pode '{0:,.2f}'.format(1333.949999999)quais impressões '1,333.95'.
Stephen Blum

@ OnurYıldırım: sim, mas você pode envolvê-lo float(); float("{0:.2f}".format(13.9499999))
21714 Jossef Harush

5
@JossefHarush você pode envolvê-lo com float (), mas você não ganhou nada. Agora você tem um carro alegórico novamente, com a mesma imprecisão. 13.9499999999999 e 13.95 são o mesmo flutuador.
Ned Batchelder

4
@NedBatchelder: eu concordo que eles são iguais, mas isso limita o float para dois pontos decimais :)
Jossef Harush

8
A propósito, desde o Python 3.6, podemos usar strings-f:f"Result is {result:.2f}"
Andrey Semakin 20/02/19

289

O built-in round()funciona bem no Python 2.7 ou posterior.

Exemplo:

>>> round(14.22222223, 2)
14.22

Confira a documentação .


1
Então, eu entendo que esta é uma falha do Python 2.7? Por que uma função tão fundamental produziria resultados diferentes das v 2.7 a v 3?
MikeM

mas round(2.16, 1)dar 2.2por python apenas oferecer uma truncatefunc
jiamo

Por exemplo, se você tentar arredondar o valor 2.675 para duas casas decimais, você receber esse >>> round(2.675, 2) 2.67 docs.python.org/2/tutorial/floatingpoint.html
danger89

4
Da página de documentação do Python 3:Note The behavior of round() for floats can be surprising: for example, round(2.675, 2) gives 2.67 instead of the expected 2.68. This is not a bug: it’s a result of the fact that most decimal fractions can’t be represented exactly as a float.
Richard Dally

Observe que, se você tentar usar esse método para imprimir um número como 1,00000, ele imprimirá apenas 1,0, independentemente de quantas casas decimais você especificar.
Josh Correia

142

Eu sinto que a abordagem mais simples é usar a format()função.

Por exemplo:

a = 13.949999999999999
format(a, '.2f')

13.95

Isso produz um número flutuante como uma string arredondada para duas casas decimais.


97

Usar

print"{:.2f}".format(a)

ao invés de

print"{0:.2f}".format(a)

Porque o último pode levar a erros de saída ao tentar gerar várias variáveis ​​(consulte os comentários).


2
Isso não faz sentido. As duas instruções fornecidas se comportam de forma idêntica no Python 2.7 e apenas a segunda é válida no Python 2.6. (Nenhuma instrução é válida em Python 3 ou Python <2.6.) A primeira forma não tem vantagem além da brevidade.
Mark Dickinson

1
Quero dizer, imprima "{0: .2f} {0: .2f}". O formato (a, b) levará a erros na saída - ele emitirá o valor 'a' duas vezes. Enquanto imprime "{:. 2f} {: .2f}". O formato (a, b) gera os valores 'a' e 'b'.
Alexey Antonenko

2
Para o Python 3, você só precisa adicionar colchetes (...). E dentro deles tudo o que escrevi está certo.
Alexey Antonenko

"Quero dizer, imprima" {0: .2f} {0: .2f} ". O formato (a, b) levará a erros na saída". Ah Bem, essa é uma afirmação bem diferente! Talvez você deva editar sua resposta? (O que significa "erro raise" média na resposta atual, por exemplo, você pode dar um exemplo de um caso em que a segunda declaração levanta uma exceção, mas o primeiro não?)
Mark Dickinson

3
Você seria depois da impressão ("{0: .2f} {1: .2f}". Formato (a, b)) se você tiver duas variáveis
Hovo

95

A maioria dos números não pode ser representada exatamente em carros alegóricos. Se você deseja arredondar o número, porque é isso que sua fórmula ou algoritmo matemático exige, você deve usar o arredondamento. Se você deseja restringir a exibição a uma certa precisão, nem use round e apenas formate-a como essa string. (Se você deseja exibi-lo com algum método alternativo de arredondamento e houver toneladas, misture as duas abordagens.)

>>> "%.2f" % 3.14159
'3.14'
>>> "%.2f" % 13.9499999
'13.95'

E, por último, embora talvez o mais importante, se você deseja matemática exata , não deseja carros alegóricos. O exemplo usual é lidar com dinheiro e armazenar 'centavos' como um número inteiro.


68

Experimente o código abaixo:

>>> a = 0.99334
>>> a = int((a * 100) + 0.5) / 100.0 # Adding 0.5 rounds it up
>>> print a
0.99

Mas tenha cuidado, o valor de a ainda é um valor impreciso. Dê uma olhada aqui - repl.it/LJs (Clique em "Executar Sessão" na parte superior da seção Direita).
lifebalance

3
Se você seguir essa abordagem, adicione 0,5 para obter uma representação mais precisa. int (a * 100 + 0,5) / 100,0; Usar math.ceil é outra opção.
Arhuaco #

3
@ShashankSawant: Bem, por um lado, a resposta apresentada não arredonda, ela trunca. A sugestão de adicionar metade no final será arredondada, mas não há benefício em fazer isso, basta usar a roundfunção em primeiro lugar. Por outro lado, como essa solução ainda usa ponto flutuante, o problema original do OP permanece, mesmo para a versão "corrigida" dessa "solução".
John Y

3
-1, isso é apenas uma reimplementação desnecessária da roundfunção (que foi usada na pergunta).
interjay 12/09/14

4
@interjay que é necessário se o round()não funcionar como o OP mencionado.
Pithikos

57

TLDR;)

O problema de arredondamento de entrada / saída foi resolvido definitivamente pelo Python 2.7.0 e 3.1 .

Um número arredondado corretamente pode ser convertido reversivelmente para frente e para trás:
str -> float() -> repr() -> float() ...ou Decimal -> float -> str -> Decimal
Um tipo decimal não é mais necessário para armazenamento.


(Naturalmente, pode ser necessário arredondar um resultado da adição ou subtração de números arredondados para eliminar os erros acumulados no último bit. Uma aritmética decimal explícita ainda pode ser útil, mas uma conversão em string por str() (ou seja, arredondar para 12 dígitos válidos) ) é bom o suficiente geralmente se não for necessária precisão extrema ou número extremo de operações aritméticas sucessivas.)

Teste infinito :

import random
from decimal import Decimal
for x in iter(random.random, None):           # Verify FOREVER that rounding is fixed :-)
    assert float(repr(x)) == x                # Reversible repr() conversion.
    assert float(Decimal(repr(x))) == x
    assert len(repr(round(x, 10))) <= 12      # Smart decimal places in repr() after round.
    if x >= 0.1:                              # Implicit rounding to 12 significant digits
        assert str(x) == repr(round(x, 12))   # by str() is good enough for small errors.
        y = 1000 * x                             # Decimal type is excessive for shopping
        assert str(y) == repr(round(y, 12 - 3))  # in a supermaket with Python 2.7+ :-)

Documentação

Veja as notas de versão Python 2.7 - Other Language Altera o quarto parágrafo:

Conversões entre números de ponto flutuante e seqüências de caracteres agora são arredondadas corretamente na maioria das plataformas. Essas conversões ocorrem em muitos lugares diferentes: str () em carros alegóricos e números complexos; os flutuadores e construtores complexos; formatação numérica; serializar e desserializar flutuadores e números complexos usando o marshal, pickleejson módulos; análise de literais flutuantes e imaginários no código Python; e conversão decimal em flutuante.

Relacionado a isso, o repr () de um número de ponto flutuante x agora retorna um resultado com base na menor string decimal que é garantida para arredondar de volta para x sob o arredondamento correto (com o modo de arredondamento de metade para metade). Anteriormente, fornecia uma string com base no arredondamento de x para 17 dígitos decimais.

A questão relacionada


Mais informações: A formatação floatanterior ao Python 2.7 era semelhante à atual numpy.float64. Ambos os tipos usam a mesma precisão dupla IEEE 754 de 64 bits com mantissa de 52 bits. Uma grande diferença é que ele np.float64.__repr__é formatado frequentemente com um número decimal excessivo, para que nenhum bit possa ser perdido, mas não existe um número IEEE 754 válido entre 13.949999999999999 e 13.950000000000001. O resultado não é bom e a conversão é formatada para que cada dígito seja importante; a sequência é sem lacunas e a conversão é reversível. Simplesmente: se você tiver um número numpy.float64, converta-o em float normal para poder ser formatado para humanos, não para processadores numéricos; caso contrário, nada mais será necessário com o Python 2.7+.repr(float(number_as_string)) não é reversível com numpy. Por outro lado:float.__repr__


Por que voto negativo? A questão era sobre Python float(precisão dupla) e normal round, não sobre numpy.double e sua conversão em string. O arredondamento simples do Python realmente não pode ser melhor do que no Python 2.7. A maioria das respostas foi escrita antes da versão 2.7, mas elas estão obsoletas, apesar de serem muito boas originalmente. Esta é a razão da minha resposta.
Hynekcer

53 bits quando você inclui o "bit oculto", implicitamente 1, exceto durante o "underflow gradual".
Rick James

Não é culpa da rodada, é a falha da tela.
Rick James

Sim, é bem conhecido. No entanto, sinto falta de um contexto se você se opuser a algo nas notas de versão do Python 2.7 ou no meu texto ou a nada. É mais complicado do que o necessário para o propósito desta questão. Deve-se acrescentar que também a conversão de string para float foi corrigida no Python 2.7 devido a um erro de arredondamento em determinados chips Intel de 32 bits e que "A função round () agora também é arredondada corretamente". ( Notas da versão - Recursos 3.1 suportados para 2.7 ). Você concorda?
Hynekcer #

1
Opa, isso foi a*bcontra b*a. Obrigado pelos links - Nostalgia.
Rick James

52

Com Python <3 (por exemplo, 2.6 ou 2.7), há duas maneiras de fazer isso.

# Option one 
older_method_string = "%.9f" % numvar

# Option two (note ':' before the '.9f')
newer_method_string = "{:.9f}".format(numvar)

Mas observe que para versões do Python acima de 3 (por exemplo, 3,2 ou 3,3), a opção dois é preferida .

Para obter mais informações sobre a opção dois, sugiro este link sobre formatação de string na documentação do Python .

E para obter mais informações sobre a opção um, este link será suficiente e terá informações sobre os vários sinalizadores .

Referência: converta o número do ponto flutuante em uma certa precisão e copie para a string


Como você representa um número inteiro? Se eu usar o formato "{i3}". (Numvar), recebo um erro.
skytux

Isto é o que eu quero dizer: Se numvar=12.456, então "{:.2f}".format(numvar)produz, 12.46mas "{:2i}".format(numvar)dá um erro e eu estou esperando 12.
skytux

50

Você pode modificar o formato de saída:

>>> a = 13.95
>>> a
13.949999999999999
>>> print "%.2f" % a
13.95

47

Ninguém aqui parece ter mencionado isso ainda, então deixe-me dar um exemplo no formato f-string / template-string do Python 3.6, que eu acho lindamente elegante:

>>> f'{a:.2f}'

Também funciona bem com exemplos mais longos, com operadores e sem necessidade de parênteses:

>>> print(f'Completed in {time.time() - start:.2f}s')


29

No Python 2.7:

a = 13.949999999999999
output = float("%0.2f"%a)
print output

1
Isso não ajuda em nada. outputtem exatamente o mesmo valor que a, então você pode ter escrito em print avez da print outputúltima linha.
Mark Dickinson

@ MarkDickinson Você poderia tentar novamente. Porque está sendo executado conforme o esperado no meu compilador.
Shashank Singh 23/09/18

1
Você está perdendo o meu ponto. Sim, seu código é impresso 13.95. Mas o mesmo acontece print acom esse valor específico de a, no Python 2.7, então não está realmente claro qual era o objetivo da etapa de formatação.
Mark Dickinson #

@ MarkDickinson Eu editei o código. Concordo que 'imprimir a' imprime o mesmo valor que "imprimir saída". Mas se você comparar "a == output", o resultado será "False" porque a etapa de formatação arredonda o valor flutuante "a" para duas casas decimais.
Shashank Singh 23/09/18

1
Você realmente tentou a == outputo código que mostra? Dá Truepara mim, e suspeito que também faz para você.
Mark Dickinson #

22

O tutorial do Python possui um apêndice chamado Aritmética de ponto flutuante: problemas e limitações . Leia-o. Ele explica o que está acontecendo e por que o Python está fazendo o seu melhor. Tem até um exemplo que corresponde ao seu. Deixe-me citar um pouco:

>>> 0.1
0.10000000000000001

você pode ficar tentado a usar a round() função para reduzi-la ao dígito que você espera. Mas isso não faz diferença:

>>> round(0.1, 1)
0.10000000000000001

O problema é que o valor do ponto flutuante binário armazenado para “0.1” já era a melhor aproximação binária possível de1/10 , portanto, tentar arredondá-lo novamente não pode torná-lo melhor: ele já .

Outra consequência é que, uma vez que 0.1 não é exatamente 1/10, a soma de dez valores de 0.1pode não render exatamente 1.0:

>>> sum = 0.0
>>> for i in range(10):
...     sum += 0.1
...
>>> sum
0.99999999999999989

Uma alternativa e solução para seus problemas seria usar o decimalmódulo.


18

Como o @Matt apontou, o Python 3.6 fornece strings-f , e eles também podem usar parâmetros aninhados :

value = 2.34558
precision = 2
width = 4

print(f'result: {value:{width}.{precision}f}')

que exibirá result: 2.35



11

Use a combinação do objeto Decimal e do método round ().

Python 3.7.3
>>> from decimal import Decimal
>>> d1 = Decimal (13.949999999999999) # define a Decimal
>>> d1 
Decimal('13.949999999999999289457264239899814128875732421875')
>>> d2 = round(d1, 2) # round to 2 decimals
>>> d2
Decimal('13.95')

7

Para corrigir o ponto flutuante em linguagens dinâmicas de tipo como Python e JavaScript, eu uso essa técnica

# For example:
a = 70000
b = 0.14
c = a * b

print c # Prints 980.0000000002
# Try to fix
c = int(c * 10000)/100000
print c # Prints 980

Você também pode usar decimal como a seguir:

from decimal import *
getcontext().prec = 6
Decimal(1) / Decimal(7)
# Results in 6 precision -> Decimal('0.142857')

getcontext().prec = 28
Decimal(1) / Decimal(7)
# Results in 28 precision -> Decimal('0.1428571428571428571428571429')

1
getcontext().prec = 6funciona apenas para o escopo da função ou para todos os lugares?
Julio Marins

1
Contextos são ambientes para operações aritméticas. Eles governam a precisão, estabelecem regras para arredondamento, determinam quais sinais são tratados como exceções e limitam o alcance dos expoentes. Cada thread tem seu próprio contexto atual @JulioMarins
Siamand 4/17

7
from decimal import Decimal


def round_float(v, ndigits=2, rt_str=False):
    d = Decimal(v)
    v_str = ("{0:.%sf}" % ndigits).format(round(d, ndigits))
    if rt_str:
        return v_str
    return Decimal(v_str)

Resultados:

Python 3.6.1 (default, Dec 11 2018, 17:41:10)
>>> round_float(3.1415926)
Decimal('3.14')
>>> round_float(3.1445926)
Decimal('3.14')
>>> round_float(3.1455926)
Decimal('3.15')
>>> round_float(3.1455926, rt_str=True)
'3.15'
>>> str(round_float(3.1455926))
'3.15'

6
orig_float = 232569 / 16000.0

14.5355625

short_float = float("{:.2f}".format(orig_float)) 

14,54


5

Que tal uma função lambda como esta:

arred = lambda x,n : x*(10**n)//1/(10**n)

Dessa forma, você pode simplesmente fazer:

arred(3.141591657,2)

e pegue

3.14

4

É simples como 1,2,3:

  1. usar decimal módulo para aritmética rápida do ponto flutuante decimal corretamente arredondado:

    d = decimal (10000000.0000009)

para obter arredondamentos:

   d.quantize(Decimal('0.01'))

resultará com Decimal('10000000.00')

  1. faça acima SECO:
    def round_decimal(number, exponent='0.01'):
        decimal_value = Decimal(number)
        return decimal_value.quantize(Decimal(exponent))

OU

    def round_decimal(number, decimal_places=2):
        decimal_value = Decimal(number)
        return decimal_value.quantize(Decimal(10) ** -decimal_places)
  1. voto positivo esta resposta :)

PS: crítica dos outros: a formatação não é arredondada.


2

Para arredondar um número para uma resolução, a melhor maneira é a seguinte, que pode funcionar com qualquer resolução (0,01 para duas casas decimais ou até outras etapas):

>>> import numpy as np
>>> value = 13.949999999999999
>>> resolution = 0.01
>>> newValue = int(np.round(value/resolution))*resolution
>>> print newValue
13.95

>>> resolution = 0.5
>>> newValue = int(np.round(value/resolution))*resolution
>>> print newValue
14.0

não funciona para mim no python 3.4.3 e numpy 1.9.1? >>> numpy importação como np >>> res = 0,01 >>> valor = 0,184 (valor >>> np.round / res) * res ,17999999999999999
szeitlin

1
Procurando documentação, vejo que o problema vem da numpy.roundexatidão / precisão. Portanto, é necessário defini-lo como int antes da multiplicação com resolução. Eu atualizei o código. Obrigado por isso!
iblasi

O único necessário é converter o numpy.float64resultado de np.round em floatou simplesmente usá-lo round(value, 2). Não existe um número IEEE 754 válido entre 13.949999999999999 (= 1395 / 100.) e 3.950000000000001 (= 1395 * .01). Por que você acha que seu método é o melhor? O valor original 13.949999999999999289 (= value = round (value, 2)) é ainda mais exato que o 13.95000000000000178 (impresso por np.float96). Mais informações também para numpy agora são adicionadas à minha resposta, que você provavelmente recebeu voto por engano. Não se tratava de numpy originalmente.
Hynekcer

@hynekcer Não acho que minha resposta seja a melhor. Só queria adicionar um exemplo de limite de flutuação para n decimais, mas o mais próximo de uma resolução definida. Eu verifiquei, como você disse, que em vez de intvocê também pode usar o floatexemplo @szeitlin. Obrigado pelo seu comentário extra. (Desculpe, mas eu não te
diminuiu

Adicionar uma dependência totalmente nova ao processamento numérico (pandas) é a "melhor maneira"?
precisa saber é o seguinte

1

lambda x, n: int (x * 10 n + .5) / 10 n trabalha há muitos anos para mim em vários idiomas.


-13

O método que eu uso é o de cortar fatias. É relativamente rápido e simples.

Primeiro, converta o flutuador em uma string, e escolha o comprimento que você deseja que seja.

float = str(float)[:5]

Na linha única acima, convertemos o valor em uma string e mantivemos a string apenas nos quatro primeiros dígitos ou caracteres (inclusive).

Espero que ajude!


2
Não poste respostas idênticas para várias perguntas.
vaultah

18
WOW ... tdh ... Por favor, nunca faça nenhum software de contabilidade ... O que acontece se o número for 113,94 ?? isso resultaria em 113,9 ... deixando 0,04 faltando ... Além disso, isso já tem respostas de mais de 5 anos atrás ....
84 84 zangado
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.