15. Aritmética de vírgula flutuante: as emissões e limitações¶

os números de vírgula flutuante são representados em hardware informático como fracções de base 2 (Binárias). Por exemplo, a fração decimal

0.125

tem valor 1/10 + 2/100 + 5/1000 e da mesma forma que a fração binária

0.001

tem valor 0/2 + 0/4 + 1/8. Estas duas frações têm valores idênticos,sendo a única diferença real que o primeiro é escrito na notação fraccional base 10, e o segundo na Base 2.

infelizmente, a maioria das fracções decimais não pode ser representada exactamente como acções Binárias. Uma consequência é que, em geral, os números decimais de ponto flutuante que você introduz só são aproximados pelos números binários de ponto flutuante armazenados na máquina.

O problema é mais fácil de entender no início na base 10. Considere o fraccionamento 1 / 3. Você pode aproximar isso como uma fração base 10:

0.3

ou, melhor,

0.33

ou, melhor,

0.333

e assim por diante. Não importa quantos dígitos você está disposto a escrever, o resultado nunca será exatamente 1/3, mas será uma aproximação cada vez melhor de 1/3.

da mesma forma, não importa quantos dígitos base 2 Você está disposto a usar, o valor decimal 0.1 não pode ser representado exatamente como uma fração base 2. Em base2, 1/10 é infinitamente fração de repetição

0.0001100110011001100110011001100110011001100110011...

Parar a qualquer número finito de bits, e você obter uma aproximação. Em mostmachines de hoje, os flutuadores são aproximados usando uma fração binária com numerador usando os primeiros 53 bits começando com o bit mais significativo e com o denominador como uma potência de dois. No caso de 1/10, o fraccionamento binário é 3602879701896397 / 2 ** 55 que é próximo mas não exatamente igual ao valor verdadeiro de 1/10.

muitos utilizadores não estão cientes da aproximação devido à forma como os valores são descarregados. O Python apenas imprime uma aproximação decimal ao valor decimal verdadeiro da aproximação binária armazenada pela máquina. Na maioria das máquinas, ifPython deveria imprimir o verdadeiro valor decimal da aproximação binária armazenada para 0.1, ele teria para exibir

>>> 0.10.1000000000000000055511151231257827021181583404541015625

Que é mais dígitos do que a maioria das pessoas acham úteis, por isso Python mantém o número de dígitos gerenciável por apresentar um valor arredondado, em vez

>>> 1 / 100.1

Apenas para lembrar, mesmo que o resultado impresso parece exato valueof 1/10, o real valor armazenado é o mais próximo representáveis fração binária.

curiosamente, existem muitos números decimais diferentes que compartilham a fração binária aproximada samenearest. Por exemplo, os números 0.1 e0.10000000000000001 e0.1000000000000000055511151231257827021181583404541015625 são allapproximated por 3602879701896397 / 2 ** 55. Uma vez que todos estes valores decimais compartilham a mesma aproximação, qualquer um deles poderia ser deslocado, preservando o invariante eval(repr(x)) == x.

historicamente, a função Python prompt e built-in repr()escolheria a que tinha 17 dígitos significativos,0.10000000000000001. A começar com o Python 3.1, Python (na maioria dos sistemas) é agora capaz de escolher o menor destes e simplesmente exibir 0.1.

Note que isto está na própria natureza do ponto flutuante binário: isto não é um python de bugin, e também não é um bug no seu código. Você verá o mesmo tipo de coisa em todas as linguagens que suportam a aritmética de ponto flutuante do seu hardware(embora algumas linguagens não possam exibir a diferença por padrão, ou em modos de saída).

para uma saída mais agradável, você pode querer usar a formatação de string para produzir um número limitado de dígitos significativos:

>>> format(math.pi, '.12g') # give 12 significant digits'3.14159265359'>>> format(math.pi, '.2f') # give 2 digits after the point'3.14'>>> repr(math.pi)'3.141592653589793'

uma ilusão pode gerar outra. Por exemplo, desde 0.1 não é exatamente 1/10,da soma de três valores de 0,1 não pode produzir exatamente 0.3, ou:

>>> .1 + .1 + .1 == .3False

além disso, desde a 0.1 não pode ficar mais próximo do valor exato de 1/10 and0.3 não é possível chegar mais perto para o valor exato da 3/10, em seguida, pré-arredondamento comround() função não pode ajudar:

>>> round(.1, 1) + round(.1, 1) + round(.1, 1) == round(.3, 1)False

Apesar de os números não podem ser feitas mais perto da sua intenção de valores exatos,o round() função pode ser útil para pós-arredondamento para que resultswith inexata valores tornam-se comparáveis a um outro:

>>> round(.1 + .1 + .1, 10) == round(.3, 10)True

Binária aritmética de ponto flutuante guarda muitas surpresas como esta. O problema com ” 0.1 “é explicado em detalhes precisos abaixo, na seção”erro de representação”. Veja os perigos do ponto flutuante para um relato mais completo de outras surpresas comuns.como isso diz perto do fim, “não há respostas fáceis.”Ainda assim,não hesite em flutuar! Os erros nas operações flutuantes em Python são herdados do hardware de ponto flutuante, e na maioria das máquinas estão na ordem de nomore do que 1 parte em 2**53 por operação. Isso é mais do que adequado para mosttasks, mas você precisa ter em mente que não é aritmética decimal e que cada operação float pode sofrer um novo erro de arredondamento.

embora existam casos patológicos, para a utilização mais casual de pointaritméticos flutuantes, irá ver o resultado que espera no final se simplesmente arredondar a difusão dos seus resultados finais para o número de dígitos decimais que espera.str() usually suffices, and for finer control see the str.format()method’s format specificers in Format String Syntax.

para os casos de uso que requerem representação decimal exata, tente usar odecimal módulo que implementa aritmética decimal adequada para aplicações contábeis e aplicações de alta precisão.

outra forma de aritmética exata é suportada pelofractions moduleque implementa aritmética baseada em números racionais (de modo que os números like1/3 podem ser representados exatamente).

Se você é um utilizador pesado de operações de vírgula flutuante, você deve dar uma olhada no Pacote numérico Python e em muitos outros pacotes para operações matemáticas e estatísticas fornecidas pelo projeto SciPy. Ver <https://scipy.org>.

Python oferece ferramentas que podem ajudar nessas raras ocasiões em que você realmente deseja saber o valor exato de um float. O métodofloat.as_integer_ratio() expressa o valor de um flutuador como afracção:

>>> x = 3.14159>>> x.as_integer_ratio()(3537115888337719, 1125899906842624)

Desde que o rácio é exata, ela pode ser usada para sem perda de dados recriar theoriginal valor:

>>> x == 3537115888337719 / 1125899906842624True

float.hex() método exprime um float em hexadecimal (base16), novamente dando a exata valor armazenado no seu computador:

>>> x.hex()'0x1.921f9f01b866ep+1'

Neste preciso representação hexadecimal pode ser usada para reconstructthe float valor exatamente:

>>> x == float.fromhex('0x1.921f9f01b866ep+1')True

Desde que a representação é exata, ela é útil para portar de forma confiável valuesacross diferentes versões do Python (independência de plataforma) e exchangingdata com outras linguagens que suportam o mesmo formato (como o Java e o C99).

outra ferramenta útil é a função math.fsum() que ajuda a mitigar a precisão durante a soma. Ele segue “Lost digits” como os valores são adicionados em um total de execução. Isso pode fazer uma diferença na precisão global, de modo que os erros não se acumulam ao ponto em que afectam o total final:

>>> sum( * 10) == 1.0False>>> math.fsum( * 10) == 1.0True

15.1. Erro de representação*

Esta secção explica o exemplo” 0.1 ” em detalhe, e mostra como você pode realizar uma análise exata de casos como este. Assume-se a familiaridade básica com a representação binarifloating-point.

erro de representação refere-se ao facto de algumas (a maioria, de facto)fracções decimais não poderem ser representadas exactamente como fracções Binárias (base 2).Esta é a principal razão pela qual Python (ou Perl, C, C++, Java, Fortran e manyothers) muitas vezes não mostra o número decimal exato que você espera.porquê? 1/10 não é exatamente representável como uma fração binária. Quase todas as linguagens de hoje (Novembro de 2000) usam aritmética de ponto flutuante IEEE-754, e quase todas as plataformas mapeiam flutuadores Python para IEEE-754 “double precision”. 754doubles contém 53 bits de precisão, de modo que na entrada o computador se esforça para toconvert 0.1 para a fração mais próxima que pode da forma J/2**N onde J isan inteiro contendo exatamente 53 bits. A reconfiguração

1 / 10 ~= J / (2**N)

como

J ~= 2**N / 10

e lembrando que J tem exatamente 53 bits (é >= 2**52 mas < 2**53),o melhor valor para N é 56:

>>> 2**52 <= 2**56 // 10 < 2**53True

isto é, 56 é o único valor de N que deixa J com exatamente 53 bits. O valor mais baixo possível para J é então o quociente arredondado:

>>> q, r = divmod(2**56, 10)>>> r6

uma vez que o restante é mais do que a metade de 10, a melhor aproximação é obtainedby arredondamento:

>>> q+17205759403792794

Portanto, o melhor possível aproximação para 1/10 em 754 precisão dupla é:

7205759403792794 / 2 ** 56

Dividindo tanto o numerador e o denominador por dois reduz a fracção:

3602879701896397 / 2 ** 55

Note que uma vez que nós arredondado, este é realmente um pouco maior do que 1/10;se não tivéssemos arredondado, o quociente teria sido um pouco menor do que 1/10. Mas em nenhum caso pode ser exatamente 1/10!

Para que o computador nunca “vê” 1/10: o que ele vê é a fração exata givenabove, o melhor 754 double aproximação pode chegar:

>>> 0.1 * 2 ** 553602879701896397.0

Se a gente multiplicar uma fração por 10**55, podemos ver o valor to55 dígitos decimais:

>>> 3602879701896397 * 10 ** 55 // 2 ** 551000000000000000055511151231257827021181583404541015625

o que significa que o número exato armazenados no computador é igual à valor decimal 0.1000000000000000055511151231257827021181583404541015625.Em vez de exibir o total valor decimal, muitas línguas (includingolder versões do Python), arredondar o resultado para 17 dígitos significativos:

>>> format(0.1, '.17f')'0.10000000000000001'

fractions e decimal módulos de fazer essas calculationseasy:

>>> from decimal import Decimal>>> from fractions import Fraction>>> Fraction.from_float(0.1)Fraction(3602879701896397, 36028797018963968)>>> (0.1).as_integer_ratio()(3602879701896397, 36028797018963968)>>> Decimal.from_float(0.1)Decimal('0.1000000000000000055511151231257827021181583404541015625')>>> format(Decimal.from_float(0.1), '.17')'0.10000000000000001'

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *