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 bien como 0.4
, pero 3*0.1
no lo es, 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')
0.3000000000000000444089209850062616169452667236328125
como0.30000000000000004
y0.40000000000000002220446049250313080847263336181640625
como.4
a pesar de que parecen tener la misma precisión, y por lo tanto no responde a la pregunta.Respuestas:
La respuesta simple se
3*0.1 != 0.3
debe a un error de cuantización (redondeo) (mientras4*0.1 == 0.4
que multiplicar por una potencia de dos suele ser una operación "exacta").Puede usar el
.hex
método 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 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, por lo tanto
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 flotador de Python 3
repr
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 se puede mostrar0.3
y0.1*3
exactamente de la misma manera, o las dos diferentes números terminaría el mismo después de ida y vuelta de. En consecuencia, elrepr
motor de Python 3 elige mostrar uno con un ligero error aparente.fuente
.hex()
; no sabía que existía).e
porque ya es un dígito hexadecimal. Tal vezp
por el poder en lugar de exponente .p
en este contexto se remonta (al menos) a C99, y también aparece en IEEE 754 y en varios otros idiomas (incluido Java). Cuandofloat.hex
yfloat.fromhex
fueron implementados (por mí :-), Python simplemente estaba copiando lo que para entonces era una práctica establecida. No sé si la intención era 'p' para "Power", pero parece una buena manera de pensarlo.repr
(ystr
en Python 3) sacará tantos dígitos como sea necesario para que el valor no sea ambiguo. En este caso, el resultado de la multiplicación3*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 otra parte, la multiplicación
4*0.1
hace obtener el valor más cercano a 0,4 (0x1.999999999999ap-2 en hexadecimal), por lo que no necesita ningún dígitos adicionales.Puede verificar esto con bastante facilidad:
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:fuente
3*0.1 == 0.3
y4*0.1 == 0.4
?Aquí hay una conclusión simplificada de otras respuestas.
fuente
str
yrepr
son idénticas para las carrozas. Para Python 2.7,repr
tiene las propiedades que identifica, perostr
es mucho más simple: simplemente calcula 12 dígitos significativos y produce una cadena de salida basada en ellos. Para Python <= 2,6, tantorepr
ystr
se basan en un número fijo de dígitos significativos (17 pararepr
, 12 parastr
). (Y a nadie le importa Python 3.0 o Python 3.1 :-)repr
este modo el comportamiento de Python 2.7 sería idéntico ...No es realmente específico para la implementación de Python, pero debería 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 de la misma manera, 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
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).
fuente