¿Por qué 1 // 0.01 == 99 en Python?

31

Me imagino que esta es una pregunta clásica de precisión de coma flotante, pero estoy tratando de entender este resultado, ejecutando 1//0.01en Python 3.7.5 rendimientos 99.

Me imagino que es un resultado esperado, pero ¿hay alguna forma de decidir cuándo es más seguro usarlo en int(1/f)lugar de hacerlo 1//f?

Albert James Teddy
fuente
44
Sí, siempre es más seguro int (1 / f). Simplemente porque // es la división FLOOR, y piensas erróneamente que es REDONDA.
Perdi Estaquel
2
No es un duplicado Esto puede funcionar como se espera 99.99% usando siempre round()y nunca //o int(). La pregunta vinculada es acerca de la comparación flotante no tiene nada que ver con el truncamiento y no hay una solución tan fácil.
maxy

Respuestas:

23

Si se tratara de una división con números reales, 1//0.01sería exactamente 100. Sin embargo, dado que son aproximaciones de punto flotante, 0.01es un poco más grande que 1/100, lo que significa que el cociente es un poco más pequeño que 100. Es este valor de 99. algo que luego se anula a 99.

chepner
fuente
3
Esto no aborda la parte "¿hay una manera de decidir cuándo es más seguro"?
Scott Hunter
10
"Más seguro" no está bien definido.
Chepner
1
Lo suficiente como para ignorarlo por completo, especialmente. cuando el OP es consciente de los problemas de coma flotante?
Scott Hunter
3
@chepner Si "más seguro" no está bien definido, entonces quizás sea mejor pedir una aclaración: /
2
para mí es bastante claro que "más seguro" significa "error no peor que una calculadora de bolsillo barata"
maxy
9

Las razones de este resultado son como usted dice, y se explican en ¿Se han roto las matemáticas de coma flotante? y muchas otras preguntas y respuestas similares.

Cuando conoce el número de decimales de numerador y denominador, una forma más confiable es multiplicar esos números primero para que puedan tratarse como enteros y luego realizar una división de enteros en ellos:

Entonces, en su caso, 1//0.01debe convertirse primero a 1*100//(0.01*100)cuál es 100.

En casos más extremos, aún puede obtener resultados "inesperados". Puede ser necesario agregar una roundllamada al numerador y al denominador antes de realizar la división entera:

1 * 100000000000 // round(0.00000000001 * 100000000000)

Pero, si se trata de trabajar con decimales fijos (dinero, centavos), entonces considere trabajar con centavos como unidad , de modo que toda la aritmética se pueda hacer como aritmética de enteros, y solo se convierta a / desde la unidad monetaria principal (dólar) al hacer E / S

O, alternativamente, use una biblioteca para decimales, como decimal , que:

... proporciona soporte para la aritmética rápida de coma flotante decimal correctamente redondeada.

from decimal import Decimal
cent = Decimal(1) / Decimal(100) # Contrary to floating point, this is exactly 0.01
print (Decimal(1) // cent) # 100
trincot
fuente
3
"que obviamente es 100". No necesariamente: si el .01 no es exacto, entonces .01 * 100 tampoco lo es. Debe "sintonizarse" manualmente.
glglgl
8

Lo que hay que tener en cuenta es que //es elfloor operador y, como tal, debe pensar primero como si tiene la misma probabilidad de caer en 100 como en 99 (*) (debido a que la operación será 100 ± epsiloncon epsilon>0la condición de que las posibilidades de conseguir exactamente 100.00 ..0 son extremadamente bajos.)

Puedes ver lo mismo con un signo menos,

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

y deberías estar tan (des) sorprendido.

Por otro lado, int(-1/.01)realiza primero la división y luego aplica int()el número, ¡que no es el piso sino un truncamiento hacia 0 ! lo que significa que en ese caso,

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

por lo tanto,

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

Sin embargo, redondear le daría SU el resultado esperado para este operador porque, nuevamente, el error es pequeño para esas cifras.

(*) No digo que la probabilidad sea la misma, solo digo que a priori cuando realizas un cálculo con aritmética flotante que es una estimación de lo que estás obteniendo.

mi radio
fuente
7

Los números de coma flotante no pueden representar la mayoría de los números decimales exactamente, por lo que cuando escribe un literal de coma flotante realmente obtiene una aproximación de ese literal. La aproximación puede ser mayor o menor que el número que escribió.

Puede ver el valor exacto de un número de coma flotante convirtiéndolo en decimal o fracción.

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

Podemos usar el tipo Fracción para encontrar el error causado por nuestro literal inexacto.

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

También podemos descubrir qué tan granulares son los números de coma flotante de precisión doble alrededor de 100 usando nextafter de numpy.

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

A partir de esto, podemos suponer que el número de coma flotante más cercano 1/0.01000000000000000020816681711721685132943093776702880859375es de hecho exactamente 100.

La diferencia entre 1//0.01y int(1/0.01)es el redondeo. 1 // 0.01 redondea el resultado exacto al siguiente número entero en un solo paso. Entonces obtenemos un resultado de 99.

int (1 / 0.01) por otro lado redondea en dos etapas, primero redondea el resultado al número de punto flotante de precisión doble más cercano (que es exactamente 100), luego redondea ese número de punto flotante al siguiente entero (que es de nuevo exactamente 100).

lavado
fuente
Llamar a esto solo redondeo es engañoso. Debería llamarse truncamiento o redondeo hacia cero : int(0.9) == 0yint(-0.9) == 0
maxy
Es binarios tipos de coma flotante que estamos hablando aquí. (También hay tipos de coma flotante decimal.)
Stephen C
3

Si ejecuta lo siguiente

from decimal import *

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

El resultado será:

99.99999999999999791833182883

Así es como se representa internamente, por lo que redondearlo hacia abajo //dará99

Lluvia
fuente
2
Es lo suficientemente preciso como para mostrar el error en este caso, pero tenga en cuenta que la aritmética "decimal" tampoco es exacta.
lavar
Cuando Decimal(0.01)llegas demasiado tarde, el error ya apareció antes de llamar Decimal. No estoy seguro de cómo se trata de una respuesta a la pregunta ... Primero debe calcular un preciso 0.01 con Decimal(1) / Decimal(100), como lo mostré en mi respuesta.
Trincot
@trincot Mi respuesta es a la pregunta del título "Por qué 1 // 0.01 == 99" Intenté mostrarle al OP cómo se tratan internamente los números flotantes.
Lluvia