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
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.