Libro Python Aplicado de Eugenia Bahit. GNU/Linux, ciencia de datos, y desarrollo web

Banner de Python Aplicado

Matemáticas nativas


Cita con formato IEEE:
E. Bahit, "El problema de los números de coma flotante y el módulo decimal", in Python Aplicado, 4th ed., EBRC Publisher, 2022, pp. 197-200.

Cita con formato APA 7:
Bahit, E. (2022). El problema de los números de coma flotante y el módulo decimal. In Python Aplicado (4th ed., pp. 197-200). EBRC Publisher.

Cita en línea:
(Bahit, 2022)

El problema de los números de coma flotante
y el modulo Decimal en Python

En 1991, David Golberg publicó un artículo denominado (en inglés) «What Every Computer Scientist Should Know About Floating-Point Arithmetic» (en español: «Lo que todo/a informático/a debería saber sobre la aritmética de coma flotante»), en el cual —entre otras cosas—, explicaba el problema del redondeo con los números de coma flotante, en el sistema binario.

Si se tuviese que resumir todo el problema planteado por Goldberg en una única frase, se podría decir que el problema de los números de coma flotante radica en tener que representar de forma finita un dato que por definición, no lo es. Pues los números racionales, son infinitos.

Como se explica en «Fundamentos de Ciencias Informáticas para el abordaje de la programación» (Bahit, 2021) al hablar de la diferencia entre las matemáticas discretas y continuas:

«(...) [al] pensar en la cantidad de números naturales que existen entre dos números (por ejemplo, entre 1 y 2) y la cantidad de números reales que existen entre los mismos dos números, enseguida será posible determinar que no existe un número finito para el segundo caso, pero que sí lo existe para el primero»

Fundamentos de Ciencias Informáticas para el abordaje de la programación by Eugenia Bahit (2021)

Entre el número 0.001 y el 0.002, existen infinitos números, pues 0.00099999999999999999999998… está entre ambos. Así es posible entender que el problema de lo infinito radica en la cantidad de decimales de un número, que puede ser infinito.

En un sistema informático, la información es representada en sistema binario, cuya unidad mínima de información es el bit (conjunción abreviada del término «binary digit»). Un bit es un dígito binario que solo puede asumir uno de dos valores posibles, el 0 o el 1. Los datos obtenidos a partir de la transformación de la información a datos binarios se denomina código binario. Existen 2n combinaciones posibles de dígitos binarios para grupos de n bits, por lo que para grupos de 8 bits existen 2^8=256 combinaciones posibles.

Teniendo en cuenta esto, para representar un número, el sistema reserva un conjunto de bits fijo (múltiplo de n, para n=8 ya que 8 bits es el tamaño de una palabra). Explica Michael Overton en «Floating Point Representation» (1996) que para almacenar un número de coma flotante se emplean, por ejemplo, palabras de 32 bits, divididas en tres:

  1. El primer bit para el signo del número
  2. los siguientes 8 bits para el exponente
  3. y los últimos 23 bits para el coeficiente

Por lo tanto, el número debe ser truncado a esta longitud, y en vez de ser una representación exacta será entonces, una representación aproximada.

Cuando la representación binaria es aproximada, su inversa también lo es. Así, se pierde precisión en la aritmética de coma flotante.

Un ejemplo de ello puede verse a continuación:

>>> 0.0001 + 0.0003
0.00039999999999999996

El cálculo anterior, ejecutado sobre una arquitectura de 64 bits, arroja un resultado aproximado al esperado 0.0004, pero no exacto, y la clase Decimal del módulo decimal de Python, ofrece mayor precisión en la aritmética de coma flotante que el tipo float.

Un ejemplo concreto de necesidad de alcanzar mayor precisión, puede darse en el ámbito financiero donde muchas veces se requiere operar con varios decimales y de la exactitud en el redondeo depende la exactitud en, por ejemplo, un balance contable o un cálculo financiero. Sin embargo, no solo se limita a este ámbito. En física y en bioinformática, de la precisión decimal dependen los resultados de un análisis u observación.

Para trabajar con estos números decimales con precisión, se puede recurrir entonces, a la clase Decimal del módulo decimal:

from decimal import Decimal
a = Decimal(0.0001)

La presión aritmética se podrá establecer mediante el atributo prec a través de la función getcontext:

>>> from decimal import Decimal, getcontext
>>> Decimal(0.1) + Decimal(0.00001)
Decimal('0.1')
>>> getcontext().prec = 2
>>> Decimal(0.1) + Decimal(0.00001)
Decimal('0.10')
>>> getcontext().prec = 3
>>> Decimal(0.1) + Decimal(0.00001)
Decimal('0.100')
>>> getcontext().prec = 4
>>> Decimal(0.1) + Decimal(0.00001)
Decimal('0.1000')
>>> getcontext().prec = 5
>>> Decimal(0.1) + Decimal(0.00001)
Decimal('0.10001')
>>> getcontext().prec = 6
>>> Decimal(0.1) + Decimal(0.00001)
Decimal('0.100010')
>>> getcontext().prec = 20
>>> Decimal(0.1) + Decimal(0.00001)
Decimal('0.10001000000000000555')

prec es un atributo (propiedad) de un tipo de objeto denominado Context, definido en el módulo decimal. Estos objetos hacen referencia por “contexto” al hilo (subproceso) en ejecución actual.
prec solo afecta al resultado de un proceso de cómputo.

Es importante notar que prec establece la precisión aritmética, pero no redondea un número no computado:

>>> Decimal(0.1)
Decimal('0.1000000000000000055511151231257827021181583404541015625')

Así, solo un número previamente computado será retornado con la precisión establecida:

>>> getcontext().prec = 2
>>> Decimal(0.1).ln()
Decimal('-2.3')