sacar - ¿Por qué math.floor(x/y)!=X// y para dos flotantes divisibles de manera uniforme en Python?
numeros divisibles python (4)
Esto no es correcto, me temo. .5 / .1 es 5.0 exactamente. Ver: (.5 / .1) .as_integer_ratio (), que produce (5,1).
Sí, 5
se puede representar como 5/1
, eso es cierto. Pero para ver la fracción del resultado real que Python da debido a la representación inexacta, siga adelante.
Primero, importa:
from decimal import *
from fractions import Fraction
Variables para fácil uso:
// as_integer_ratio() returns a tuple
xa = Decimal((.5).as_integer_ratio()[0])
xb = Decimal((.5).as_integer_ratio()[1])
ya = Decimal((.1).as_integer_ratio()[0])
yb = Decimal((.1).as_integer_ratio()[1])
Rinde los siguientes valores:
xa = 1
xb = 2
ya = 3602879701896397
yb = 36028797018963968
Naturalmente, 1/2 == 5
y 3602879701896397 / 36028797018963968 == 0.1000000000000000055511151231
.
Entonces, ¿qué pasa cuando dividimos?
>>> print (xa/xb)/(ya/yb)
4.999999999999999722444243845
Pero cuando queremos la relación de enteros ...
>>> print float((xa/xb)/(ya/yb)).as_integer_ratio()
(5, 1)
Como se dijo anteriormente, 5
es 5/1
por supuesto. Ahí es donde entra la Fraction
:
>>> print Fraction((xa/xb)/(ya/yb))
999999999999999944488848769/200000000000000000000000000
Y Wolfram alfa confirma que esto es de hecho 4.999999999999999722444243845
.
¿Por qué no haces Fraction(.5/.1)
o Fraction(Decimal(.5)/Decimal(.1))
?
Este último nos dará el mismo resultado 5/1
. El primero nos dará 1249999999999999930611060961/250000000000000000000000000
en 1249999999999999930611060961/250000000000000000000000000
lugar. Esto da como resultado 4.999999999999999722444243844
, similar pero no el mismo resultado.
He estado leyendo sobre la división y la división entera en Python y las diferencias entre la división en Python2 vs Python3. En su mayor parte, todo tiene sentido. Python 2 usa la división de enteros solo cuando ambos valores son enteros. Python 3 siempre realiza una verdadera división. Python 2.2+ introdujo el operador //
para la división entera.
Ejemplos que otros programadores han ofrecido para trabajar de forma agradable y ordenada, tales como:
>>> 1.0 // 2.0 # floors result, returns float
0.0
>>> -1 // 2 # negatives are still floored
-1
¿Cómo se implementa //
? ¿Por qué sucede lo siguiente?
>>> import math
>>> x = 0.5
>>> y = 0.1
>>> x / y
5.0
>>> math.floor(x/y)
5.0
>>> x // y
4.0
¿No debería x // y = math.floor(x/y)
? Estos resultados se produjeron en python2.7, pero como xey son ambos flotadores, los resultados deberían ser los mismos en python3 +. Si hay algún error de coma flotante donde x/y
es en realidad 4.999999999999999
y math.floor(4.999999999999999) == 4.0
¿no se reflejaría eso en x/y
?
Los siguientes casos similares, sin embargo, no se ven afectados:
>>> (.5*10) // (.1*10)
5.0
>>> .1 // .1
1.0
El problema es que Python redondeará la salida como se describe aquí . Como 0.1
no se puede representar exactamente en binario, el resultado es algo así como 4.999999999999999722444243844000
. Naturalmente esto se convierte en 5.0
cuando no se usa el formato
Eso es porque
>>> .1
0.10000000000000001
.1
no se puede representar con precisión en binario
También puedes ver que
>>> .5 / 0.10000000000000001
5.0
No encontré las otras respuestas satisfactorias. Claro, .1
no tiene una expansión binaria finita, por lo que nuestro presentimiento es que el error de representación es el culpable. Pero ese presentimiento solo no explica realmente por qué math.floor(.5/.1)
produce 5.0
mientras que .5 // .1
produce 4.0
.
La frase clave es que a // b
está haciendo floor((a - (a % b))/b)
, en lugar de simplemente floor(a/b)
.
.5 / .1 es exactamente 5.0
En primer lugar, tenga en cuenta que el resultado de .5 / .1
es exactamente 5.0
en Python. Este es el caso aunque .1
no puede ser representado exactamente. Tome este código, por ejemplo:
from decimal import Decimal
num = Decimal(.5)
den = Decimal(.1)
res = Decimal(.5/.1)
print(''num: '', num)
print(''den: '', den)
print(''res: '', res)
Y la salida correspondiente:
num: 0.5
den: 0.1000000000000000055511151231257827021181583404541015625
res: 5
Esto muestra que .5
puede representarse con una expansión binaria finita, pero .1
no puede. Pero también muestra que a pesar de esto, el resultado de .5 / .1
es exactamente 5.0
. Esto se debe a que la división de punto flotante produce una pérdida de precisión y la cantidad en que den
difiere de .1
se pierde en el proceso.
Es por eso que math.floor(.5 / .1)
funciona como se podría esperar: como .5 / .1
es 5.0
, escribir math.floor(.5 / .1)
es lo mismo que escribir math.floor(5.0)
.
Entonces, ¿por qué no .5 // .1
resulta en 5?
Se podría suponer que .5 // .1
es una abreviatura de floor(.5 / .1)
, pero este no es el caso. Como resultado, la semántica difiere. Esto es a pesar de que el PEP dice :
La división de piso se implementará en todos los tipos numéricos de Python y tendrá la semántica de
a // b == floor(a/b)
Como resultado, la semántica de .5 // .1
es en realidad equivalente a:
floor((.5 - mod(.5, .1)) / .1)
donde mod
es el resto de punto flotante de .5 / .1
redondeado hacia cero. Esto queda claro al leer el código fuente de Python .
Aquí es donde el problema es que el hecho de que .1
no pueda ser representado exactamente por una expansión binaria. El resto de punto flotante de .5 / .1
no es cero:
>>> .5 % .1
0.09999999999999998
y tiene sentido que no lo sea. Dado que la expansión binaria de .1
es ligeramente mayor que el decimal real .1
, el alpha
entero más grande tal que alpha * .1 <= .5
(en nuestra matemática de precisión finita) es alpha = 4
. Entonces mod(.5, .1)
no es cero, y es aproximadamente .1
. Por lo tanto, floor((.5 - mod(.5, .1)) / .1)
convierte en floor((.5 - .1) / .1)
convierte en floor(.4 / .1)
que es igual a 4
.
Y es por eso que .5 // .1 == 4
.
¿Por qué //
hace eso?
El comportamiento de a // b
puede parecer extraño, pero hay una razón para que sea divergente de math.floor(a/b)
. En su blog sobre la historia de Python, Guido escribe:
La operación de división de enteros (//) y su hermano, la operación de módulo (%), van juntas y satisfacen una buena relación matemática (todas las variables son enteros):
a/b = q with remainder r
tal que
b*q + r = a and 0 <= r < b
(suponiendo que a y b son> = 0).
Ahora, Guido asume que todas las variables son enteros, pero esa relación se mantendrá si a
y b
son flotantes, si q = a // b
. Si q = math.floor(a/b)
la relación no se mantendrá en general. Y entonces //
podría ser preferible porque satisface esta buena relación matemática.