Desmitificando el punto flotante: 0.10000000000000000555

Sí, hoy toca hablar sobre los números de coma flotante o de punto flotante. Y lo voy a hacer de una forma bastante informal y con ciertas licencias para ver si los podemos entender y podemos entender los potenciales peligros que pueden tener para nuestros cálculos.

Bueno, empecemos por el final: -“¡¡¡Todos hemos muerto!!!”-,… Vale, no hace falta ir tal al final, un poco antes.

Si yo hago lo siguiente:

El resultado que se mostrará debería ser algo como lo siguiente:

Vemos que hay algo raro. Yo esperaría que el valor de c fuera 0.3 pero no sale exactamente eso.

Para añadir un poco más de confusión, si yo hago lo siguiente:

El resultado muestra:

Veo que \(0.3\) se puede representar…

¿Por qué \(0.1+0.2\) no da exactamente \(0.3\)? Vamos a ver a, b, c y cc con un poco más de detalle. Voy a extender el número de decimales que se muestran en pantalla:

El resultado será:

Vemos que a no es exactamente 0.1. Lo mismo sucede con b, que no es exactamente 0.2. El problema es que en el print inicial no estaba mostrando el número completo que se usa internamente, solo una representación más amigable que suele ser correcta en el 99% (valor inventado) de los usos que hacemos de estos números donde no necesitamos una precisión muy alta. Por otro lado, también vemos que el resultado de sumar 0.1 y 0.2, c, difiere levemente a si defino directamente una variable usando el valor de 0.3, cc.

-“Muy bien, pero no has explicado porqué sucede esto.”-

Tienes razón. Pues volvamos al principio: -“Hace trece mil ochocientos millones de años hubo una gran explosión…”-. Vale, ya paro, no hace falta irse tan atrás. Me centro. Los números de punto flotante.

Punto flotante desde el punto de vista IEEE754

Lo de punto flotante viene del inglés floating point. Un número de estos es un número en los que el punto decimal puede moverse a izquierda o derecha o que no hay un número determinado de dígitos a izquierda/antes o derecha/después de ese punto decimal. Es decir, ese punto ‘flota’ y, dependiendo de los números que estemos usando, tendremos precisiones que varían. Más sobre esto después.

Los números de punto flotante pretenden representar a los números reales dentro de ese complejo artefacto que llamamos computadora. Pero, como alguna avispada lectora o algún avispado lector se habrá dado cuenta, los números reales son infinitos. ¿Cómo puedo representar \(1/3\) (0.33333333333…) y no perder decimales y precisión? Pues resulta que tenemos una memoria finita y una operación como \(1/3+1/3\) no la podemos hacer con nuestros ordenadores y tener un número infinito de decimales…

Necesitamos soluciones de compromiso que valgan para la inmensa mayoría de los problemas. Es por ello que se usa un estándar que se conoce como IEEE754. Su última versión es la del 2019 y es el estándar que adopta la mayoría de lenguajes de programación. Este estándar vale para representar números usando un número limitado de bits pero también para hacer operaciones aritméticas básicas con ellos (e.g., \(0.1+0.2\)).

En el anterior párrafo he destacado lo de número limitado de bits. Los números de punto flotante se pueden representar con distintas precisiones, si necesitamos más precisión seguramente necesitemos más bits para almacenar esa precisión extra y, por tanto, más memoria para almacenarlos y más procesador para procesarlos. No necesitamos la misma gasolina para mover un coche o un camión ni necesitamos el mismo espacio para aparcar un coche o un camión,…

Por ejemplo, para guardar un float de 32 bits o 64 bits se usa algo como lo siguiente:

Fuente: Wikipedia
Fuente: Wikipedia

¿Qué significan los colores? Los colores son diferentes partes de un número que se puede representar como:

$$signo \space b^{exponente} \space mantisa$$

¿Qué es eso de signo, exponente, mantisa? Por eficiencia de cómo se construyen los procesadores se suelen usar representaciones binarias para que podamos hacer más cosas con nuestros limitados recursos.

  • Signo: Es el signo del número y se representa usando el último bit que tengamos. En un número de 32 bits será el bit 32, en uno de 64 bits será el bit 64, etcétera. ¿Cómo se cálcula? Si ese último bit es 1 o 0 tendremos que el signo será \((−1)^1=−1\) o \((−1)^0=+1\), respectivamente.
  • La parte \(b^{exponente}\): Esa parte nos permite definir el rango del número que está a la izquierda del punto decimal. b puede ser 2 o 10 dependiendo de si trabajamos con base 2 o con base 10. Como he comentado, es más eficiente trabajar con binarios. El exponente se obtiene de los últimos 8 bits para un float de 32 bits, de los últimos 11 bits para un float 64 bits,…, (sin contar el último bit que corresponde al signo). El exponente puede tener un rango que va desde Emin a Emax siendo Emin = 1 - Emax. Si tenemos un número de 32 bits, tenemos 8 bits para representar el exponente, el Emax se calcula como \(2^{(bits para el exponente)−1}−1\), por tanto, para un número de 32 bits, en el que tenemos 8 bits para el exponente, tenemos Emax = 2**(8-1) - 1 = 127 y Emin = 1 - Emax = 1 - 127 = -126. y el exponente se obtiene con el valor de los 8 bits y al que le restamos el valor de Emax.
  • La mantisa o significando: La mantisa es lo que representaría lo que va detrás del punto flotante. Dependiendo de si está normalizado o no será de una forma u otra.

Y me dirás: –Pues excepto lo del signo, que es más o menos sencillo, sigo sin entender absolutamente nada de nada y leyendo la wikipedia es lo mismo o mejor-. Normal, porque me explico fatal, porque sois impacientes y porque no es sencillo de entender con esta explicación tan mala y simplificada que he hecho.

(continuará…)

1 comentario en «Desmitificando el punto flotante: 0.10000000000000000555»

Deja una respuesta

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

54 − forty seven =