15. Aritmética de Coma flotante: Problemas y limitaciones¶

Los números de coma flotante se representan en el hardware de la computadora como fracciones base 2 (binarias). Por ejemplo, la fracción decimal

0.125

tiene un valor 1/10 + 2/100 + 5/1000 y de la misma manera que la fracción binaria

0.001

tiene un valor 0/2 + 0/4 + 1/8. Estas dos fracciones tienen valores idénticos, la única diferencia real es que la primera se escribe en notación fraccionaria de base 10,y la segunda en base 2.

Desafortunadamente, la mayoría de las fracciones decimales no se pueden representar exactamente como fracciones binarias. Una consecuencia es que, en general, los números decimales de punto flotante que ingresa solo se aproximan por los números binarios de punto flotante almacenados en la máquina.

El problema es más fácil de entender al principio en base 10. Considere la fracción1 / 3. Puede aproximarlo como una fracción de base de 10:

0.3

o, mejor,

0.33

o, mejor,

0.333

y así sucesivamente. No importa cuántos dígitos esté dispuesto a escribir, el resultado nunca será exactamente 1/3, sino que será una aproximación cada vez mejor de 1/3.

De la misma manera, no importa cuántos dígitos de base 2 esté dispuesto a usar, el valor decimal 0.1 no se puede representar exactamente como una fracción de base 2. En base2, 1/10 es infinitamente fracción de repetición

0.0001100110011001100110011001100110011001100110011...

Detener a cualquier número finito de bits, y se obtiene una aproximación. En la mayoría de las máquinas de hoy, los flotadores se aproximan usando una fracción binaria con el numerador usando los primeros 53 bits comenzando con el bit más significativo y con el denominador como una potencia de dos. En el caso de 1/10, el fraccionamiento binario es 3602879701896397 / 2 ** 55 que es cercano pero no exactamente igual al valor verdadero de 1/10.

Muchos usuarios no son conscientes de la aproximación debido a la forma en que se muestran los valores. Python solo imprime una aproximación decimal al valor decimal verdadero de la aproximación binaria almacenada por la máquina. En la mayoría de las máquinas, ifPython debía imprimir el verdadero valor decimal de la aproximación binaria almacenada para 0.1, tendría que mostrar

>>> 0.10.1000000000000000055511151231257827021181583404541015625

Que son más dígitos de los que la mayoría de la gente encuentra útiles, por lo que Python mantiene el número de dígitos manejable mostrando un valor redondeado en su lugar

>>> 1 / 100.1

Solo recuerde, a pesar de que el resultado impreso se parece al valor exacto de 1/10, el valor almacenado real es la fracción binaria representable más cercana.

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 y0.10000000000000001 y0.1000000000000000055511151231257827021181583404541015625 son allapproximated por 3602879701896397 / 2 ** 55. Dado que todos estos valores decimales comparten la misma aproximación, cualquiera de ellos podría mostrarse mientras conserva el invariante eval(repr(x)) == x.

Históricamente, el prompt de Python y la función incorporada repr() elegirían una con 17 dígitos significativos, 0.10000000000000001. Empezando por Python 3.1, Python (en la mayoría de los sistemas) ahora puede elegir el más corto de estos y simplemente mostrar 0.1.

Tenga en cuenta que esto está en la naturaleza misma del punto flotante binario: esto no es un error en Python, y tampoco es un error en su código. Verás el mismo tipo de cosas en todos los idiomas que admiten la aritmética de coma flotante de tu hardware(aunque es posible que algunos idiomas no muestren la diferencia de forma predeterminada o en los modos de salida total).

Para una salida más agradable, es posible que desee utilizar el formato de cadena para producir un número limitado de dígitos significativos:

>>> format(math.pi, '.12g') # give 12 significant digits'3.14159265359'>>> format(math.pi, '.2f') # give 2 digits after the point'3.14'>>> repr(math.pi)'3.141592653589793'

Es importante darse cuenta de que esto es, en un sentido real, una ilusión: está redondeando simplemente la visualización del valor verdadero de la máquina.

Una ilusión puede engendrar otra. Por ejemplo, dado que 0.1 no es exactamente 1/10,sumar tres valores de 0.1 puede no producir exactamente 0.3, ya sea:

>>> .1 + .1 + .1 == .3False

También, dado que 0.1 no puede acercarse al valor exacto de 1/10 y 0.3 no puede acercarse al valor exacto de 3/10, luego redondear conround() la función no puede ayudar:

>>> round(.1, 1) + round(.1, 1) + round(.1, 1) == round(.3, 1)False

Aunque los números no se pueden acercar a sus valores exactos previstos,la funciónround()puede ser útil para el redondeo posterior, de modo que los resultados con valores inexactos sean comparables entre sí:

>>> round(.1 + .1 + .1, 10) == round(.3, 10)True

La aritmética de coma flotante binaria contiene muchas sorpresas como esta. El problema con «0.1» se explica en detalle a continuación, en la sección «Error de representación». Vea Los Peligros de la Coma flotante Para una explicación más completa de otras sorpresas comunes.

Como dice al final, » no hay respuestas fáciles.»¡Aún así, no tengas miedo de la coma flotante! Los errores en las operaciones de flotación de Python se heredan del hardware de punto flotante, y en la mayoría de las máquinas son del orden de no más de 1 parte en 2**53 por operación. Eso es más que adecuado para la mayoría de las tareas, pero debe tener en cuenta que no es aritmética decimal y que cada operación flotante puede sufrir un nuevo error de redondeo.

Si bien existen casos patológicos, para el uso más informal de aritmética de punto flotante, verá el resultado que espera al final si simplemente redondea la visualización de sus resultados finales al número de dígitos decimales que espera.str() suele ser suficiente, y para un control más preciso, consulte los especificadores de formato del método str.format()en la sintaxis de cadena de formato.

Para casos de uso que requieren una representación decimal exacta, intente usar el módulodecimal que implementa aritmética decimal adecuada para aplicaciones de contabilidad y aplicaciones de alta precisión.

Otra forma de aritmética exacta es compatible con el módulo fractions que implementa aritmética basada en números racionales (por lo que los números como 1/3 se pueden representar exactamente).

Si usted es un usuario intensivo de operaciones de coma flotante, debería echar un vistazo al paquete Numérico Python y a muchos otros paquetes para operaciones matemáticas y estadísticas suministrados por el proyecto SciPy. Ver <https://scipy.org>.

Python proporciona herramientas que pueden ayudar en esas raras ocasiones en las que realmente quieres saber el valor exacto de un flotador. El métodofloat.as_integer_ratio() expresa el valor de un flotador como afracción:

>>> x = 3.14159>>> x.as_integer_ratio()(3537115888337719, 1125899906842624)

Dado que la relación es exacta, puede ser utilizado para sin pérdida de recrear theoriginal valor:

>>> x == 3537115888337719 / 1125899906842624True

El float.hex() método expresa un flotador en hexadecimal (base16), de nuevo dando el valor exacto que se almacena en su equipo:

>>> x.hex()'0x1.921f9f01b866ep+1'

Este preciso representación hexadecimal se puede utilizar para reconstructthe valor float exactamente:

>>> x == float.fromhex('0x1.921f9f01b866ep+1')True

Dado que la representación es exacta, es útil para portar valores de forma fiable en diferentes versiones de Python (independencia de plataforma) e intercambiar datos con otros lenguajes que admiten el mismo formato (como Java y C99).

Otra herramienta útil es la función math.fsum() que ayuda a mitigar la pérdida de precisión durante la suma. Rastrea «dígitos perdidos» a medida que los valores se agregan a un total en ejecución. Eso puede hacer una diferencia en la precisión general para que los errores no se acumulen hasta el punto en que afecten al total final:

>>> sum( * 10) == 1.0False>>> math.fsum( * 10) == 1.0True

15.1. Error de representación¶

Esta sección explica en detalle el ejemplo» 0.1 » y muestra cómo puede realizar usted mismo un análisis exacto de casos como este. Se asume la familiaridad básica con la representación de puntos de flotación binaria.

Error de representación se refiere al hecho de que algunas fracciones decimales (la mayoría, en realidad)no se pueden representar exactamente como fracciones binarias (base 2).Esta es la razón principal por la que Python (o Perl, C, C++, Java, Fortran y muchos otros) a menudo no muestra el número decimal exacto que espera.

¿por Qué es eso? 1/10 no es exactamente representable como una fracción binaria. Casi todas las máquinas de hoy (noviembre de 2000) usan aritmética de coma flotante IEEE-754, y casi todas las plataformas mapean flotadores de Python a IEEE-754 de «doble precisión». 754dubles contienen 53 bits de precisión, por lo que al ingresar la computadora se esfuerza por convertir 0.1 a la fracción más cercana que pueda de la forma J/2**N donde J es un entero que contiene exactamente 53 bits. La reescritura

1 / 10 ~= J / (2**N)

como

J ~= 2**N / 10

y recordando que J tiene exactamente 53 bits (es >= 2**52 pero < 2**53),el mejor valor de N es de 56:

>>> 2**52 <= 2**56 // 10 < 2**53True

es decir, 56 es el único valor de N, que deja a J con exactamente 53 bits. El mejor valor posible para J es entonces ese cociente redondeado:

>>> q, r = divmod(2**56, 10)>>> r6

Puesto que el resto es más de la mitad de 10, la mejor aproximación es obtainedby redondeo:

>>> q+17205759403792794

por lo Tanto, la mejor aproximación posible a 1/10 en 754 de doble precisión es:

7205759403792794 / 2 ** 56

Dividiendo el numerador y el denominador por dos reduce la fracción a:

3602879701896397 / 2 ** 55

tenga en cuenta que desde que nos redondeado hacia arriba, esto es en realidad un poco más grande que el 1/10;si no hubiéramos redondeado, el cociente habría sido un poco menor que 1/10. Pero en ningún caso puede ser exactamente 1/10!

Para que la computadora nunca «vea» 1/10: lo que ve es la fracción exacta dada anteriormente, la mejor aproximación doble de 754 que puede obtener:

>>> 0.1 * 2 ** 553602879701896397.0

Si multiplicamos esa fracción por 10**55, podemos ver el valor a 55 dígitos decimales:

>>> 3602879701896397 * 10 ** 55 // 2 ** 551000000000000000055511151231257827021181583404541015625

lo que significa que el número exacto almacenado en el equipo es igual al valor decimal 0.1000000000000000055511151231257827021181583404541015625.En lugar de mostrar el completo valor decimal, muchos idiomas (includingolder versiones de Python), redondear el resultado a 17 dígitos significativos:

>>> format(0.1, '.17f')'0.10000000000000001'

El fractions y decimal módulos de hacer estos calculationseasy:

>>> from decimal import Decimal>>> from fractions import Fraction>>> Fraction.from_float(0.1)Fraction(3602879701896397, 36028797018963968)>>> (0.1).as_integer_ratio()(3602879701896397, 36028797018963968)>>> Decimal.from_float(0.1)Decimal('0.1000000000000000055511151231257827021181583404541015625')>>> format(Decimal.from_float(0.1), '.17')'0.10000000000000001'

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *