Por que 1 // 0,01 == 99 no Python?

31

Eu imagino que seja uma pergunta clássica de precisão de ponto flutuante, mas estou tentando entender esse resultado, rodando 1//0.01nos rendimentos do Python 3.7.5 99.

Imagino que seja um resultado esperado, mas existe alguma maneira de decidir quando é mais seguro usar int(1/f)do que 1//f?

Albert James Teddy
fonte
4
Sim, é sempre mais seguro int (1 / f). Simplesmente porque // é a divisão FLOOR e você pensa erroneamente nela como ROUND.
Perdi Estaquel 21/02
4
Possível duplicata de A matemática do ponto flutuante está quebrada?
pppery 21/02
2
Não é uma duplicata. Isso pode funcionar como esperado 99,99%, sempre usando round()e nunca //ou int(). A questão vinculada é sobre a comparação de flutuação não tem nada a ver com truncamento e não tem uma solução tão fácil.
maxy 1/03

Respostas:

23

Se fosse uma divisão com números reais, 1//0.01seria exatamente 100. Já que são aproximações de ponto flutuante, 0.01é um pouco maior que 1/100, o que significa que o quociente é um pouco menor que 100. É esse valor de 99. algo que é calculado a 99.

chepner
fonte
3
Isso não trata da parte "existe uma maneira de decidir quando é mais seguro".
Scott Hunter
10
"Mais seguro" não está bem definido.
chepner 20/02
11
O suficiente para ignorá-lo completamente, esp. quando o OP está ciente dos problemas de ponto flutuante?
Scott Hunter
3
@chepner Se "mais seguro" não estiver bem definido, talvez seja melhor pedir esclarecimentos: /
2
está bem claro para mim que "mais seguro" significa "erro não pior que uma calculadora de bolso barata"
maxy
9

As razões para esse resultado são como você declara e são explicadas em A matemática do ponto flutuante está quebrada? e muitas outras perguntas e respostas semelhantes.

Quando você souber o número de casas decimais do numerador e do denominador, uma maneira mais confiável é multiplicar esses números primeiro para que possam ser tratados como números inteiros e, em seguida, realizar a divisão inteira neles:

Portanto, no seu caso, 1//0.01deve ser convertido primeiro para o 1*100//(0.01*100)qual é 100.

Em casos mais extremos, você ainda pode obter resultados "inesperados". Pode ser necessário adicionar uma roundchamada ao numerador e denominador antes de executar a divisão inteira:

1 * 100000000000 // round(0.00000000001 * 100000000000)

Mas, se se trata de trabalhar com decimais fixos (dinheiro, centavos), considere trabalhar com centavos como unidade , para que toda a aritmética possa ser feita como aritmética inteira e somente converter para / da unidade monetária principal (dólar) ao fazer E / S.

Ou, como alternativa, use uma biblioteca para decimais, como decimal , que:

... fornece suporte para aritmética rápida de ponto flutuante decimal corretamente arredondado.

from decimal import Decimal
cent = Decimal(1) / Decimal(100) # Contrary to floating point, this is exactly 0.01
print (Decimal(1) // cent) # 100
trincot
fonte
3
"que obviamente é 100." Não necessariamente: se o 0,01 não é exato, 0,01 * 100 também não. Ele deve ser "sintonizado" manualmente.
glglgl 20/02
8

O que você tem que levar em conta é que //é o flooroperador e como tal, você deve primeiro pensar como se você tem igual probabilidade de cair em 100 como em 99 (*) (porque a operação será 100 ± epsiloncom epsilon>0desde que as chances de conseguir exatamente 100,00 ..0 são extremamente baixos.)

Você pode ver o mesmo com um sinal de menos,

>>> 1//.01
99.0
>>> -1//.01
-100.0

e você deve ser tão (des) surpreso.

Por outro lado, int(-1/.01)executa primeiro a divisão e depois aplica int()o número, que não é mínimo, mas um truncamento para 0 ! o que significa que, nesse caso,

>>> 1/.01
100.0
>>> -1/.01
-100.0

conseqüentemente,

>>> int(1/.01)
100
>>> int(-1/.01)
-100

O arredondamento, porém, forneceria o resultado esperado do SEU para esse operador, porque, novamente, o erro é pequeno para esses números.

(*) Não estou dizendo que a probabilidade é a mesma, apenas estou dizendo que, a priori, quando você realiza tal cálculo com aritmética flutuante, é uma estimativa do que você está obtendo.

myradio
fonte
7

Os números de ponto flutuante não podem representar exatamente a maioria dos números decimais, portanto, ao digitar um literal de ponto flutuante, você realmente obtém uma aproximação desse literal. A aproximação pode ser maior ou menor que o número digitado.

Você pode ver o valor exato de um número de ponto flutuante convertendo-o para decimal ou fração.

>>> from decimal import Decimal
>>> Decimal(0.01)
Decimal('0.01000000000000000020816681711721685132943093776702880859375')
>>> from fractions import Fractio
>>> Fraction(0.01)
Fraction(5764607523034235, 576460752303423488) 

Podemos usar o tipo Fraction para encontrar o erro causado por nosso literal inexato.

>>> float((Fraction(1)/Fraction(0.01)) - 100)
-2.0816681711721685e-15

Também podemos descobrir como são os números de ponto flutuante de precisão dupla granular em torno de 100 usando nextafter from numpy.

>>> from numpy import nextafter
>>> nextafter(100,0)-100
-1.4210854715202004e-14

A partir disso, podemos supor que o número do ponto flutuante mais próximo 1/0.01000000000000000020816681711721685132943093776702880859375seja de fato exatamente 100.

A diferença entre 1//0.01e int(1/0.01)é o arredondamento. 1 // 0,01 arredonda o resultado exato para o próximo número inteiro em uma única etapa. Então, obtemos um resultado de 99.

int (1 / 0,01), por outro lado, arredonda em dois estágios, primeiro arredonda o resultado para o número de ponto flutuante de precisão dupla mais próximo (que é exatamente 100), depois arredonda esse número de ponto flutuante para o próximo número inteiro (que é novamente exatamente 100).

plugwash
fonte
Chamar isso de arredondamento é enganoso. Deve ser chamado de truncamento ou arredondamento para zero : int(0.9) == 0eint(-0.9) == 0
maxy
É do tipo de ponto flutuante binário que você está falando aqui. (Também existem tipos decimais de ponto flutuante.)
Stephen C
3

Se você executar o seguinte

from decimal import *

num = Decimal(1) / Decimal(0.01)
print(num)

A saída será:

99.99999999999999791833182883

É assim que é representado internamente, então, arredondá-lo para baixo //dará99

Chuva
fonte
2
É preciso o suficiente para mostrar o erro nesse caso, mas lembre-se de que a aritmética "decimal" também não é exata.
plugwash
Se Decimal(0.01)você chegar tarde demais, o erro já aparecerá antes de você ligar Decimal. Não tenho certeza de como essa é uma resposta para a pergunta ... Você deve primeiro calcular 0,01 preciso Decimal(1) / Decimal(100), como mostrei na minha resposta.
trincot 21/02
@trincot Minha resposta é para a pergunta no título "Por que 1 // 0,01 == 99" Tentei mostrar ao OP como os números flutuantes são tratados internamente.
Rain