Me imagino que esta es una pregunta clásica de precisión de punto flotante, pero estoy tratando de entender este resultado, ejecutar 1//0.01
en Python 3.7.5 produce 99
.
Me imagino que es un resultado esperado, pero ¿hay alguna forma de decidir cuándo es más seguro usar int(1/f)
en lugar de 1//f
?
Si se tratara de una división con números reales, 1//0.01
sería exactamente 100. Sin embargo, dado que son aproximaciones de punto flotante, 0.01
es 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 99.algo valor que luego se reduce a 99.
Las razones de este resultado son como usted dice, y se explican en ¿Están rotas las matemáticas de punto flotante? y muchas otras preguntas y respuestas similares.
Cuando conoce la cantidad de decimales del numerador y el denominador, una forma más confiable es multiplicar esos números primero para que puedan tratarse como enteros y luego realizar una división entera en ellos:
Entonces, en su caso, 1//0.01
debe convertirse primero en 1*100//(0.01*100)
, que es 100.
En casos más extremos, aún puede obtener resultados "inesperados". Puede ser necesario agregar una llamada round
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), considere trabajar con centavos como unidad , de modo que toda la aritmética se pueda hacer como aritmética de enteros, y solo 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 aritmética de coma flotante decimal redondeada correctamente.
from decimal import Decimal cent = Decimal(1) / Decimal(100) # Contrary to floating point, this is exactly 0.01 print (Decimal(1) // cent) # 100
Lo que tienes que tener en cuenta es que //
es el operador de floor
y como tal primero debes pensar que tienes la misma probabilidad de caer en 100 que en 99 (*) (porque la operación será 100 ± epsilon
con epsilon>0
siempre que las posibilidades de obtener exactamente 100.00..0 sean extremadamente bajas).
De hecho, puedes ver lo mismo con un signo menos,
>>> 1//.01 99.0 >>> -1//.01 -100.0
y deberías estar como (no) sorprendido.
Por otro lado, int(-1/.01)
realiza primero la división y luego aplica el int()
en el número, que no es piso sino un truncamiento hacia 0 ! lo que significa que en ese caso,
>>> 1/.01 100.0 >>> -1/.01 -100.0
por eso,
>>> int(1/.01) 100 >>> int(-1/.01) -100
Sin embargo, el redondeo le daría SU 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 de este tipo con aritmética flotante, es una estimación de lo que obtienes.
Si ejecutas lo siguiente
from decimal import * num = Decimal(1) / Decimal(0.01) print(num)
La salida será:
99.99999999999999791833182883
Así es como se representa internamente, por lo que redondearlo hacia abajo //
dará 99
Los números de coma flotante no pueden representar exactamente la mayoría de los números decimales, por lo que cuando escribe un literal de coma flotante, en realidad 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 Fraction para encontrar el error causado por nuestro literal inexacto.
>>> float((Fraction(1)/Fraction(0.01)) - 100) -2.0816681711721685e-15
También podemos averiguar qué tan granulares son los números de punto flotante de doble precisión alrededor de 100 usando nextafter de numpy.
>>> from numpy import nextafter >>> nextafter(100,0)-100 -1.4210854715202004e-14
De esto podemos suponer que el número de punto flotante más cercano a 1/0.01000000000000000020816681711721685132943093776702880859375
es, de hecho, exactamente 100.
La diferencia entre 1//0.01
e 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 coma flotante de precisión doble más cercano (que es exactamente 100), luego redondea ese número de coma flotante al siguiente entero (que es otra vez exactamente 100).