Visualizador de patrones de bits en punto flotante IEEE 754

Descompone un número decimal en su patrón de bits IEEE 754 de precisión simple (32 bits) o doble (64 bits), o decodifica un patrón de bits de vuelta a un valor decimal. El signo, el exponente y la mantisa se muestran con colores diferenciados, con soporte para NaN, infinito y números subnormales.

El ejemplo clásico: por qué 0.1 + 0.2 no da 0.3

En JavaScript, `0.1 + 0.2` da como resultado `0.30000000000000004`, no `0.3`. Observar los patrones de bits en precisión doble explica por qué.

Valor Hexadecimal en precisión doble (64 bits) Binario en precisión doble (64 bits)
0.1 0x3FB999999999999A 0011111110111001100110011001100110011001100110011001100110011010
0.2 0x3FC999999999999A 0011111111001001100110011001100110011001100110011001100110011010
0.3 0x3FD3333333333333 0011111111010011001100110011001100110011001100110011001100110011
0.1 + 0.2 0x3FD3333333333334 0011111111010011001100110011001100110011001100110011001100110100

0.1, 0.2 y 0.3 son todas fracciones infinitamente periódicas en binario, así que ninguna cabe exactamente en la mantisa de 52 bits y cada una se redondea. Como los errores de redondeo de 0.1 y 0.2 no llegan a cancelarse entre sí, su suma termina siendo apenas un bit distinta del patrón exacto de 0.3 (0x3FD3333333333334). Prueba a introducir estos valores en el conversor de arriba y compara tú mismo los últimos bits.

Consejos

  • Convierte 0.1, 0.2 y 0.3 a bits en precisión doble y compáralos con el resultado de 0.1+0.2 (0.30000000000000004): podrás ver a simple vista que solo difiere el último bit.
  • Introducir "-0" en el campo decimal produce un patrón de bits con solo el bit de signo activado (distinto de "+0"). Como números, 0 === -0, pero las representaciones en bits son distintas.
  • Cuando el campo del exponente es todo cero, una mantisa igual a cero significa cero, y una mantisa distinta de cero significa un número subnormal, una representación especial usada para alcanzar valores extremadamente cercanos a cero a costa de la precisión.
  • Alternar entre precisión simple (32 bits) y doble (64 bits) para un mismo valor como 0.1 muestra cómo el error de redondeo es mayor en precisión simple.
  • Para decodificar un patrón de bits de vuelta a un valor decimal, introduce una cadena hexadecimal con el prefijo `0x`, o una cadena binaria con exactamente tantos dígitos como la precisión seleccionada (el prefijo `0b` es opcional).

Preguntas frecuentes

0.1, 0.2 y 0.3 son todas fracciones infinitamente periódicas en binario, así que una computadora tiene que redondear cada una para que quepa en la mantisa finita (52 bits en precisión doble). Como los errores de redondeo de 0.1 y 0.2 no llegan a cancelarse entre sí, su suma termina siendo ligeramente distinta del patrón de bits exacto de 0.3 (0.30000000000000004). No es un error de ningún lenguaje en particular: ocurre en todos los lenguajes que cumplen con IEEE 754.

La precisión simple (float, 32 bits) usa 1 bit de signo, 8 bits de exponente y 23 bits de mantisa. La precisión doble (double, 64 bits) usa 1 bit de signo, 11 bits de exponente y 52 bits de mantisa. Más bits de mantisa dan más cifras significativas (precisión), y más bits de exponente dan un rango representable más amplio. La mayoría de los lenguajes de programación usan precisión doble de forma predeterminada para su tipo de punto flotante.

Es un patrón de bits en el que el campo del exponente es todo cero pero la mantisa no es cero. Un número de punto flotante normal tiene un 1 implícito al principio de su mantisa, pero un número subnormal lo omite y en su lugar fija el exponente en su valor mínimo, lo que permite representar valores incluso más cercanos a cero de lo que puede el número normal más pequeño, a costa de algo de precisión.

IEEE 754 tiene un bit de signo independiente, así que incluso cuando el exponente y la mantisa son todos cero, un bit de signo igual a 0 da "+0" y un bit de signo igual a 1 da "-0": dos patrones de bits distintos. Numéricamente, 0 === -0 es verdadero, pero algunas operaciones revelan la diferencia, como que 1/0 produzca +Infinity mientras que 1/-0 produzca -Infinity.

Ambos usan un patrón de bits en el que el campo del exponente es todo unos. Si la mantisa es todo ceros, representa infinito (con el bit de signo distinguiendo +Infinity de -Infinity); si la mantisa no es cero, representa NaN (Not a Number, no es un número). Surgen de operaciones matemáticamente indefinidas como la división entre cero o ∞ - ∞.
ツールくん

A propósito — Cómo IEEE 754 se convirtió en un lenguaje común

Antes de 1985, cada fabricante de computadoras usaba su propia representación de punto flotante. IBM, DEC, Cray y otros podían calcular el mismo valor, digamos "0.1", y obtener un comportamiento de redondeo y rangos representables sutilmente distintos, por lo que portar un programa numérico de una máquina a otra a menudo producía resultados ligeramente diferentes. Para resolver este caos, un grupo de trabajo liderado por William Kahan, de UC Berkeley —quien también había participado en el diseño del coprocesador numérico Intel 8087—, impulsó la creación de un estándar a escala de toda la industria, publicado en 1985 como IEEE 754.

La idea central de IEEE 754 es dividir un número finito de bits (32 o 64) en tres roles —signo, exponente y mantisa— para aproximar el conjunto infinito de los números reales. El exponente se encarga de la escala (cuán grande o pequeño es un número), mientras que la mantisa se encarga de la precisión (cuántas cifras significativas tiene), lo que permite que un número fijo de bits cubra con flexibilidad tanto magnitudes muy pequeñas como muy grandes. El exponente se almacena sumándole un sesgo (127 en precisión simple, 1023 en precisión doble) para que comparar dos patrones de bits como simples enteros sin signo baste para determinar cuál valor es mayor, evitando la complejidad adicional de una representación con signo como el complemento a dos.

Solo las fracciones cuyo denominador es una potencia de dos (1/2, 1/4, 3/8, etc.) pueden representarse exactamente en binario. El valor decimal 0.1 es la fracción 1/10, y como 10 no es una potencia de dos, se convierte en la fracción binaria infinitamente periódica 0.0001100110011... Con solo un número finito de bits de mantisa disponibles, hay que truncarla y redondearla en algún punto: esta es la razón fundamental por la que una computadora no puede representar 0.1 exactamente. También es la razón por la que los sistemas contables que necesitan aritmética decimal exacta suelen evitar los números de punto flotante en favor de la aritmética con enteros o un tipo decimal dedicado (como BCD).

IEEE 754 se revisó más tarde en 2008 y 2019, añadiendo precisión media (16 bits), precisión cuádruple (128 bits) y formatos de punto flotante decimal, pero la división central en signo, exponente y mantisa, junto con sus modos de redondeo, no ha cambiado desde la edición original de 1985. Como prácticamente todos los tipos float/double de los lenguajes de programación se ajustan a este estándar, la pregunta "¿por qué 0.1 + 0.2 no es igual a 0.3?" aparece prácticamente en la misma forma sin importar el lenguaje que uses.