Módulo de python en flotadores

¿Alguien puede explicar cómo funciona el operador de módulo en Python? No entiendo por qué 3.5 % 0.1 = 0.1 .

En realidad, no es cierto que el 3.5 % 0.1 sea 0.1 . Puedes probar esto muy fácilmente:

 >>> print(3.5 % 0.1) 0.1 >>> print(3.5 % 0.1 == 0.1) False 

En la actualidad, en la mayoría de los sistemas, el 3.5 % 0.1 es 0.099999999999999811 . Pero, en algunas versiones de Python, str(0.099999999999999811) es 0.1 :

 >>> 3.5 % 0.1 0.099999999999999811 >>> repr(3.5 % 0.1) '0.099999999999999811' >>> str(3.5 % 0.1) '0.1' 

Ahora, probablemente te estarás preguntando por qué 3.5 % 0.1 es 0.099999999999999811 lugar de 0.0 . Esto se debe a los problemas habituales de redondeo de punto flotante. Si no ha leído Lo que todo científico informático debería saber sobre la aritmética de punto flotante , debería, o al menos el breve resumen de Wikipedia sobre este tema en particular.

Tenga en cuenta también que 3.5/0.1 no es 34 , es 35 . Entonces, 3.5/0.1 * 0.1 + 3.5%0.1 es 3.5999999999999996 , que ni siquiera está cerca de 3.5 . Esto es bastante fundamental para la definición de módulo, y está mal en Python, y en casi todos los demás lenguajes de progtwigción.

Pero Python 3 viene al rescate allí. La mayoría de las personas que saben sobre // saben que es la forma en que se hace la “división de enteros” entre enteros, pero no se dan cuenta de que se trata de cómo se hace la división compatible con módulos entre cualquier tipo. 3.5//0.1 es 34.0 , entonces 3.5//0.1 * 0.1 + 3.5%0.1 es (al menos dentro de un pequeño error de redondeo de) 3.5 . Esto se ha implementado en la versión 2.x, por lo que (dependiendo de su versión y plataforma exactas) puede confiar en esto. Y, si no, puede usar divmod(3.5, 0.1) , que devuelve (dentro del error de redondeo) (34.0, 0.09999999999999981) hasta el final de la noche. Por supuesto, aún esperaba que esto fuera (35.0, 0.0) , no (34.0, almost-0.1) , pero no puede tenerlo debido a errores de redondeo.

Si está buscando una solución rápida, considere usar el tipo Decimal :

 >>> from decimal import Decimal >>> Decimal('3.5') % Decimal('0.1') Decimal('0.0') >>> print(Decimal('3.5') % Decimal('0.1')) 0.0 >>> (Decimal(7)/2) % (Decimal(1)/10) Decimal('0.0') 

Esto no es una panacea mágica; por ejemplo, aún tendrá que lidiar con el error de redondeo siempre que el valor exacto de una operación no sea finamente representable en la base 10, pero los errores de redondeo se alinean mejor con los casos que la intuición humana espera ser problematico (También hay ventajas en Decimal over float que puede especificar precisiones explícitas, rastrear dígitos significativos, etc., y en realidad es el mismo en todas las versiones de Python de 2.4 a 3.3, mientras que los detalles sobre float han cambiado dos veces en el mismo). Es solo que no es perfecto, porque eso sería imposible.) Pero cuando sabe de antemano que todos sus números se pueden representar exactamente en la base 10, y no necesitan más dígitos que la precisión que ha configurado, trabajará.

Modulo te da el rest de una división. 3.5 dividido por 0.1 debería darte 35 con un rest de 0 . Pero como los flotadores se basan en potencias de dos, los números no son exactos y se obtienen errores de redondeo.

Si necesita que su división de números decimales sea exacta, use el módulo decimal:

 >>> from decimal import Decimal >>> Decimal('3.5') / Decimal('0.1') Decimal('35') >>> Decimal('3.5') % Decimal('0.1') Decimal('0.0') 

Cuando me están diciendo que mi respuesta es engañosa aquí viene toda la historia:

0.1 es ligeramente más grande que 0.1

 >>> '%.50f' % 0.1 '0.10000000000000000555111512312578270211815834045410' 

Si divide el flotador 3.5 por dicho número, obtendrá un descanso de casi 0.1 .

Comencemos con el número 0.11 y sigamos agregando ceros entre los dos dígitos para reducirlo mientras se mantiene más grande que 0.1 .

 >>> '%.10f' % (3.5 % 0.101) '0.0660000000' >>> '%.10f' % (3.5 % 0.1001) '0.0966000000' >>> '%.10f' % (3.5 % 0.10001) '0.0996600000' >>> '%.10f' % (3.5 % 0.100001) '0.0999660000' >>> '%.10f' % (3.5 % 0.1000001) '0.0999966000' >>> '%.10f' % (3.5 % 0.10000001) '0.0999996600' >>> '%.10f' % (3.5 % 0.100000001) '0.0999999660' >>> '%.10f' % (3.5 % 0.1000000001) '0.0999999966' >>> '%.10f' % (3.5 % 0.10000000001) '0.0999999997' >>> '%.10f' % (3.5 % 0.100000000001) '0.1000000000' 

La última línea da la impresión de que finalmente hemos alcanzado 0.1 pero cambiar las cadenas de formato revela la verdadera naturaleza:

 >>> '%.20f' % (3.5 % 0.100000000001) '0.09999999996600009156' 

El formato flotante predeterminado de python simplemente no muestra la precisión suficiente para que el 3.5 % 0.1 = 0.1 y el 3.5 % 0.1 = 35.0 . Realmente es 3.5 % 0.100000... = 0.999999... y 3.5 / 0.100000... = 34.999999.... En el caso de la división, incluso terminas con el resultado exacto, ya que 34.9999... se redondea finalmente a 35.0 .


Dato curioso: si usa un número que es un poco más pequeño que 0.1 y realiza la misma operación, terminará con un número que es un poco más grande que 0 :

 >>> 1.0 - 0.9 0.09999999999999998 >>> 35.0 % (1.0 - 0.9) 7.771561172376096e-15 >>> '%.20f' % (35.0 % (1.0 - 0.9)) '0.00000000000000777156' 

Usando C ++ puedes incluso mostrar que 3.5 dividido por el flotante 0.1 no es 35 sino algo un poco más pequeño.

 #include  #include  int main(int argc, char *argv[]) { // double/float, rounding errors do not cancel out std::cout << "double/float: " << std::setprecision(20) << 3.5 / 0.1f << std::endl; // double/double, rounding errors cancel out std::cout << "double/double: " << std::setprecision(20) << 3.5 / 0.1 << std::endl; return 0; } 

http://ideone.com/fTNVho

En Python 3.5 / 0.1 te da el resultado exacto de 35 porque los errores de redondeo se anulan entre sí. Realmente es 3.5 / 0.100000... = 34.9999999... Y 34.9999... es en definitiva tan largo que terminas con exactamente 35 . El progtwig C ++ lo muestra muy bien, ya que puedes mezclar doble y flotar y jugar con las precisiones de los números de punto flotante.

Tiene que ver con la naturaleza inexacta de la aritmética de punto flotante. 3.5 % 0.1 me consigue 0.099999999999999811 , por lo que Python piensa que 0.1 se divide en 3.5 a lo sumo 34 veces, quedando 0.099999999999999811. No estoy seguro de qué algoritmo se está utilizando para lograr este resultado, pero eso es lo esencial.