Eu sei que a maioria dos decimais não tem uma representação exata de ponto flutuante (a matemática do ponto flutuante está quebrada? ).
Mas não vejo por que 4*0.1
é bem impresso 0.4
, mas 3*0.1
não é, quando ambos os valores realmente têm representações decimais feias:
>>> 3*0.1
0.30000000000000004
>>> 4*0.1
0.4
>>> from decimal import Decimal
>>> Decimal(3*0.1)
Decimal('0.3000000000000000444089209850062616169452667236328125')
>>> Decimal(4*0.1)
Decimal('0.40000000000000002220446049250313080847263336181640625')
0.3000000000000000444089209850062616169452667236328125
como0.30000000000000004
e0.40000000000000002220446049250313080847263336181640625
como.4
, embora eles parecem ter a mesma precisão, e, portanto, não responder à pergunta.Respostas:
A resposta simples é
3*0.1 != 0.3
devido ao erro de quantização (arredondamento) (enquanto a4*0.1 == 0.4
multiplicação por uma potência de dois geralmente é uma operação "exata").Você pode usar o
.hex
método em Python para visualizar a representação interna de um número (basicamente, o valor exato do ponto flutuante binário, em vez da aproximação da base 10). Isso pode ajudar a explicar o que está acontecendo sob o capô.0,1 é 0x1,999999999999a vezes 2 ^ -4. O "a" no final significa o dígito 10 - em outras palavras, 0,1 no ponto flutuante binário é muito ligeiramente maior que o valor "exato" de 0,1 (porque o 0x0,99 final é arredondado para 0x0.a). Quando você multiplica isso por 4, uma potência de dois, o expoente muda (de 2 ^ -4 para 2 ^ -2), mas o número permanece inalterado
4*0.1 == 0.4
.No entanto, quando você multiplica por 3, a pequena diferença entre 0x0,99 e 0x0.a0 (0x0,07) aumenta para um erro 0x0.15, que aparece como um erro de um dígito na última posição. Isso faz com que 0,1 * 3 seja muito ligeiramente maior que o valor arredondado de 0,3.
O float do Python 3
repr
foi projetado para ser trip de ida e volta , ou seja, o valor mostrado deve ser exatamente conversível no valor original. Portanto, ele não pode exibir0.3
e0.1*3
exatamente da mesma maneira, ou os dois diferentes números iria acabar o mesmo depois de round-trip. Conseqüentemente, orepr
mecanismo do Python 3 escolhe exibir um com um pequeno erro aparente.fonte
.hex()
, eu não sabia que existia.)e
porque isso já é um dígito hexadecimal. Talvezp
por poder em vez de expoente .p
neste contexto remonta (pelo menos) a C99 e também aparece no IEEE 754 e em várias outras linguagens (incluindo Java). Quandofloat.hex
efloat.fromhex
foram implementados (por mim :-), o Python estava apenas copiando o que era então uma prática estabelecida. Não sei se a intenção era 'p' para "Power", mas parece uma boa maneira de pensar sobre isso.repr
(estr
no Python 3) exibirá quantos dígitos forem necessários para tornar o valor inequívoco. Nesse caso, o resultado da multiplicação3*0.1
não é o valor mais próximo de 0,3 (0x1.3333333333333p-2 em hexadecimal); na verdade, é um LSB maior (0x1.333333333333334p-2), portanto, é necessário mais dígitos para diferenciá-lo de 0,3.Por outro lado, a multiplicação
4*0.1
faz obter o valor mais próximo a 0,4 (0x1.999999999999ap-2 em hexadecimal), por isso não precisa de dígitos adicionais.Você pode verificar isso facilmente:
Usei a notação hexadecimal acima porque é agradável e compacta e mostra a diferença de bits entre os dois valores. Você pode fazer isso sozinho usando, por exemplo
(3*0.1).hex()
. Se você preferir vê-los em toda a sua glória decimal, aqui está:fonte
3*0.1 == 0.3
e4*0.1 == 0.4
?Aqui está uma conclusão simplificada de outras respostas.
fonte
str
erepr
são idênticas para os carros alegóricos. Para o Python 2.7,repr
possui as propriedades que você identifica, masstr
é muito mais simples - simplesmente calcula 12 dígitos significativos e produz uma string de saída com base neles. Para Python <= 2.6, ambosrepr
estr
são baseados em um número fixo de dígitos significativos (17 pararepr
, 12 parastr
). (E ninguém se preocupa com Python 3.0 ou Python 3.1 :-)repr
, assim, o comportamento Python 2.7 seria idêntico ...Não é realmente específico para a implementação do Python, mas deve se aplicar a qualquer função float para string decimal.
Um número de ponto flutuante é essencialmente um número binário, mas em notação científica com um limite fixo de números significativos.
O inverso de qualquer número que possua um fator de número primo que não seja compartilhado com a base sempre resultará em uma representação de ponto pontual recorrente. Por exemplo, 1/7 tem um fator primo, 7, que não é compartilhado com 10 e, portanto, tem uma representação decimal recorrente, e o mesmo vale para 1/10 com os fatores primos 2 e 5, o último não sendo compartilhado com 2 ; isso significa que 0,1 não pode ser representado exatamente por um número finito de bits após o ponto.
Como 0.1 não tem representação exata, uma função que converte a aproximação em uma sequência de pontos decimais geralmente tentará aproximar certos valores para que eles não obtenham resultados não intuitivos como 0.1000000000004121.
Como o ponto flutuante está em notação científica, qualquer multiplicação por uma potência da base afeta apenas a parte expoente do número. Por exemplo, 1.231e + 2 * 100 = 1.231e + 4 para notação decimal e, da mesma forma, 1.00101010e11 * 100 = 1.00101010e101 em notação binária. Se eu multiplicar por uma não potência da base, os dígitos significativos também serão afetados. Por exemplo 1.2e1 * 3 = 3.6e1
Dependendo do algoritmo usado, ele pode tentar adivinhar decimais comuns com base apenas nos números significativos. Tanto 0,1 como 0,4 têm os mesmos números significativos em binário, porque seus flutuadores são essencialmente truncamentos de (8/5) (2 ^ -4) e (8/5) (2 ^ -6), respectivamente. Se o algoritmo identificar o padrão 8/5 sigfig como o decimal 1.6, ele funcionará em 0.1, 0.2, 0.4, 0.8, etc. Ele também poderá ter padrões mágicos sigfig para outras combinações, como o float 3 dividido pelo float 10 e outros padrões mágicos estatisticamente prováveis de serem formados pela divisão por 10.
No caso de 3 * 0,1, os últimos números significativos provavelmente serão diferentes da divisão de um flutuador 3 por flutuador 10, fazendo com que o algoritmo não reconheça o número mágico da constante 0,3, dependendo de sua tolerância à perda de precisão.
Editar: https://docs.python.org/3.1/tutorial/floatingpoint.html
Não há tolerância para a perda de precisão, se float x (0,3) não for exatamente igual a float y (0,1 * 3), então repr (x) não será exatamente igual a repr (y).
fonte