float decimales python floating-point rounding floating-accuracy ieee-754

decimales - ¿Por qué el valor de coma flotante de 4*0.1 se ve bien en Python 3 pero 3*0.1 no?



decimales en python (4)

Aquí hay una conclusión simplificada de otras respuestas.

Si marca un flotante en la línea de comando de Python o lo imprime, pasa por la función repr que crea su representación de cadena.

A partir de la versión 3.2, los str y repr Python utilizan un esquema de redondeo complejo, que prefiere decimales de aspecto agradable si es posible, pero usa más dígitos cuando sea necesario para garantizar el mapeo biyectivo (uno a uno) entre flotantes y sus representaciones de cadenas.

Este esquema garantiza que el valor de repr(float(s)) ve bien para decimales simples, incluso si no se pueden representar con precisión como flotantes (por ejemplo, cuando s = "0.1") .

Al mismo tiempo, garantiza que float(repr(x)) == x mantiene para cada float x

Sé que la mayoría de los decimales no tienen una representación exacta de coma flotante ( ¿se rompen las matemáticas de coma flotante? ).

Pero no veo por qué 4*0.1 se imprime tan bien como 0.4 , pero 3*0.1 no, cuando ambos valores tienen representaciones decimales feas:

>>> 3*0.1 0.30000000000000004 >>> 4*0.1 0.4 >>> from decimal import Decimal >>> Decimal(3*0.1) Decimal(''0.3000000000000000444089209850062616169452667236328125'') >>> Decimal(4*0.1) Decimal(''0.40000000000000002220446049250313080847263336181640625'')


La respuesta simple es porque 3*0.1 != 0.3 debido a un error de cuantificación (redondeo) (mientras que 4*0.1 == 0.4 porque multiplicar por una potencia de dos suele ser una operación "exacta").

Puede usar el método .hex en Python para ver la representación interna de un número (básicamente, el valor exacto de punto flotante binario, en lugar de la aproximación de base 10). Esto puede ayudar a explicar lo que sucede debajo del capó.

>>> (0.1).hex() ''0x1.999999999999ap-4'' >>> (0.3).hex() ''0x1.3333333333333p-2'' >>> (0.1*3).hex() ''0x1.3333333333334p-2'' >>> (0.4).hex() ''0x1.999999999999ap-2'' >>> (0.1*4).hex() ''0x1.999999999999ap-2''

0.1 es 0x1.999999999999a veces 2 ^ -4. La "a" al final significa el dígito 10; en otras palabras, 0.1 en coma flotante binaria es muy ligeramente mayor que el valor "exacto" de 0.1 (porque el 0x0.99 final se redondea a 0x0.a). Cuando multiplica esto por 4, una potencia de dos, el exponente se mueve hacia arriba (de 2 ^ -4 a 2 ^ -2) pero el número no cambia, entonces 4*0.1 == 0.4 .

Sin embargo, cuando multiplica por 3, la pequeña pequeña diferencia entre 0x0.99 y 0x0.a0 (0x0.07) aumenta en un error de 0x0.15, que se muestra como un error de un dígito en la última posición. Esto hace que 0.1 * 3 sea un poco más grande que el valor redondeado de 0.3.

El float repr Python 3 está diseñado para ser de ida y vuelta , es decir, el valor que se muestra debe ser exactamente convertible en el valor original. Por lo tanto, no puede mostrar 0.3 y 0.1*3 exactamente de la misma manera, o los dos números diferentes terminarían igual después del disparo de ida y vuelta. En consecuencia, el motor repr Python 3 elige mostrar uno con un ligero error aparente.


No es realmente específico para la implementación de Python, pero debe aplicarse a cualquier función de cadena flotante a decimal.

Un número de coma flotante es esencialmente un número binario, pero en notación científica con un límite fijo de cifras significativas.

El inverso de cualquier número que tenga un factor de número primo que no se comparta con la base siempre dará como resultado una representación de punto de punto recurrente. Por ejemplo, 1/7 tiene un factor primo, 7, que no se comparte con 10 y, por lo tanto, tiene una representación decimal recurrente, y lo mismo es cierto para 1/10 con los factores primos 2 y 5, este último no se comparte con 2 ; Esto significa que 0.1 no puede representarse exactamente por un número finito de bits después del punto de punto.

Como 0.1 no tiene una representación exacta, una función que convierte la aproximación a una cadena de punto decimal generalmente intentará aproximar ciertos valores para que no obtengan resultados no intuitivos como 0.1000000000004121.

Dado que el punto flotante está en notación científica, cualquier multiplicación por una potencia de la base solo afecta a la parte exponente del número. Por ejemplo, 1.231e + 2 * 100 = 1.231e + 4 para notación decimal, y del mismo modo, 1.00101010e11 * 100 = 1.00101010e101 en notación binaria. Si multiplico por un no poder de la base, los dígitos significativos también se verán afectados. Por ejemplo 1.2e1 * 3 = 3.6e1

Dependiendo del algoritmo utilizado, puede tratar de adivinar decimales comunes basados ​​solo en cifras significativas. Tanto 0.1 como 0.4 tienen las mismas cifras significativas en binario, porque sus flotadores son esencialmente truncamientos de (8/5) (2 ^ -4) y (8/5) (2 ^ -6) respectivamente. Si el algoritmo identifica el patrón sigfig 8/5 como el decimal 1.6, entonces funcionará en 0.1, 0.2, 0.4, 0.8, etc. También puede tener patrones sigfig mágicos para otras combinaciones, como el flotador 3 dividido por el flotador 10 y otros patrones mágicos estadísticamente propensos a formarse por división por 10.

En el caso de 3 * 0.1, las últimas cifras significativas probablemente serán diferentes de dividir un flotador 3 por el flotador 10, haciendo que el algoritmo no reconozca el número mágico para la constante 0.3 dependiendo de su tolerancia a la pérdida de precisión.

Editar: https://docs.python.org/3.1/tutorial/floatingpoint.html

Curiosamente, hay muchos números decimales diferentes que comparten la misma fracción binaria aproximada más cercana. Por ejemplo, los números 0.1 y 0.10000000000000001 y 0.1000000000000000055511151231257827021181583404541015625 están aproximados por 3602879701896397/2 ** 55. Dado que todos estos valores decimales comparten la misma aproximación, cualquiera de ellos podría mostrarse mientras se conserva la evaluación invariante (repr) (repr) ) == x.

No hay tolerancia para la pérdida de precisión, si float x (0.3) no es exactamente igual a float y (0.1 * 3), entonces repr (x) no es exactamente igual a repr (y).


repr (y str en Python 3) colocará tantos dígitos como sea necesario para que el valor no sea ambiguo. En este caso, el resultado de la multiplicación 3*0.1 no es el valor más cercano a 0.3 (0x1.3333333333333p-2 en hexadecimal), en realidad es un LSB más alto (0x1.3333333333334p-2), por lo que necesita más dígitos para distinguirlo de 0.3.

Por otro lado, la multiplicación 4*0.1 obtiene el valor más cercano a 0.4 (0x1.999999999999ap-2 en hexadecimal), por lo que no necesita ningún dígito adicional.

Puede verificar esto con bastante facilidad:

>>> 3*0.1 == 0.3 False >>> 4*0.1 == 0.4 True

Utilicé la notación hexadecimal anterior porque es agradable y compacta y muestra la diferencia de bits entre los dos valores. Puede hacerlo usted mismo usando, por ejemplo, (3*0.1).hex() . Si prefieres verlos en toda su gloria decimal, aquí tienes:

>>> Decimal(3*0.1) Decimal(''0.3000000000000000444089209850062616169452667236328125'') >>> Decimal(0.3) Decimal(''0.299999999999999988897769753748434595763683319091796875'') >>> Decimal(4*0.1) Decimal(''0.40000000000000002220446049250313080847263336181640625'') >>> Decimal(0.4) Decimal(''0.40000000000000002220446049250313080847263336181640625'')