⏱️ Lectura: 11 min
Si alguna vez abriste una consola de Python, JavaScript o Rust y escribiste 0.1 + 0.2, ya conocés el resultado: 0.30000000000000004. Este pequeño error, que ha confundido a generaciones de desarrolladores, no es un bug ni un descuido del compilador. Es una consecuencia directa del estándar IEEE 754, la norma internacional que rige cómo casi todas las computadoras del planeta representan números floating point en binario. En 2026, con la inteligencia artificial empujando los formatos numéricos hacia precisiones cada vez más reducidas como FP8 y FP4, entender bien los floating point dejó de ser un detalle académico para convertirse en una habilidad ingenieril crítica.
📑 En este artículo
- Qué es el floating point IEEE 754
- Anatomía de un float bit a bit
- El famoso 0.1 + 0.2 que no da 0.3
- Precisiones IEEE 754: half, float, double y la era del FP8
- Errores comunes con floating point en producción
- El futuro: FP4, microscaling y los posits
- Preguntas frecuentes
- ¿Por qué los lenguajes no usan otra forma de representar decimales por defecto?
- ¿Es seguro usar double para representar dinero?
- ¿Qué es un NaN y cuándo aparece?
- ¿Qué diferencia hay entre underflow y overflow?
- ¿Vale la pena aprender FP8/FP4 si no trabajo en machine learning?
- ¿Por qué Python muestra 0.1 como 0.1 si internamente es 0.10000000000000000555?
- Referencias
A lo largo de este artículo vamos a desarmar el IEEE 754 desde sus fundamentos. Vamos a ver cómo se construye un float bit a bit, por qué tu CPU literalmente no puede almacenar el número 0.1 con exactitud, qué diferencia hay entre half, float, double y los formatos comprimidos del mundo IA, y cómo evitar los errores numéricos más comunes que afectan sistemas de pagos, simulaciones físicas y modelos de machine learning.
Qué es el floating point IEEE 754
El estándar IEEE 754 fue publicado por el Institute of Electrical and Electronics Engineers en 1985, después de una década de fragmentación entre fabricantes. Antes de IEEE 754, IBM, DEC, Cray y muchos otros tenían formatos propios e incompatibles: el mismo programa podía dar resultados distintos al portarlo de una máquina a otra. La adopción del estándar unificó la industria y hoy es prácticamente universal. Cualquier dispositivo donde estés leyendo este artículo —laptop, teléfono, servidor cloud, GPU NVIDIA— usa IEEE 754 binary para coma flotante.
La idea central es sencilla: en lugar de almacenar dígitos con una posición fija para la coma decimal, los floating point usan notación científica binaria. Un número como 1001.0101 en base 2 se reescribe como 1.0010101 × 2³, y se guardan tres componentes: signo, exponente y mantisa (también llamada significand). Lo «flotante» del nombre viene de ahí: el punto binario flota según el valor del exponente, lo que permite representar tanto 6.5×10⁻¹⁰ como 7.3×10⁹ con la misma cantidad de bits.
La revisión IEEE 754-2008 introdujo el formato binary16 (half precision) y el binary128 (quad precision). La revisión 2019 sumó decimal floating point. Y en 2023, el grupo de trabajo OFP8 del Open Compute Project estandarizó FP8 para machine learning. La historia del IEEE 754 sigue activa cuatro décadas después, justamente porque la inteligencia artificial cambió las reglas del juego.
Anatomía de un float bit a bit
Tomemos un float estándar de 32 bits, también llamado binary32 o single precision. Sus 32 bits se distribuyen así:
- 1 bit para el signo (0 = positivo, 1 = negativo).
- 8 bits para el exponente, con un sesgo (bias) de 127.
- 23 bits para la mantisa, con un bit «implícito» de 1 al inicio que no se almacena.
Eso da una mantisa efectiva de 24 bits y un rango de exponentes que va desde -126 hasta +127. La fórmula para reconstruir el número es:
valor = (-1)^signo × 1.mantisa × 2^(exponente - 127)
graph LR
F["Float 32 bits"] --> S["1 bit signo"]
F --> E["8 bits exponente (bias 127)"]
F --> M["23 bits mantisa"]
S --> V["valor = signo × 1.mantisa × 2^(exp-127)"]
E --> V
M --> V
Veamos un ejemplo. Si querés representar el decimal 6.25 como un float, primero lo convertís a binario: 110.01. Luego lo normalizás corriendo el punto: 1.1001 × 2². El signo es 0 (positivo), el exponente codificado es 2 + 127 = 129 = 10000001 en binario, y la mantisa son los bits que siguen al punto: 1001 seguido de 19 ceros. El bit más a la izquierda de la mantisa —ese 1 implícito— nunca se almacena porque siempre sería 1 en notación científica binaria.
El resultado es que un float ofrece aproximadamente 7 dígitos decimales de precisión, mientras que un double de 64 bits (con 52 bits de mantisa) llega a 15-17 dígitos. La diferencia es dramática para cálculos científicos pero a veces irrelevante para gráficos en tiempo real, donde un float es más que suficiente y consume la mitad de memoria.
El famoso 0.1 + 0.2 que no da 0.3
Ahora sí: la pregunta del millón. ¿Por qué 0.1 + 0.2 es 0.30000000000000004 en lugar de 0.3?
El problema es que el número 0.1 no tiene representación exacta en binario. Igual que 1/3 en decimal nos da 0.3333… infinito, 1/10 en binario es un decimal periódico: 0.0001100110011001100... infinito. Como solo tenés 52 bits de mantisa en un double, ese número se trunca y queda almacenado como una aproximación cercana pero no exacta: aproximadamente 0.1000000000000000055511151231257827021181583404541015625.
Cuando sumás esa versión truncada de 0.1 con la versión truncada de 0.2 (que también arrastra su propio error), los pequeños errores se acumulan y obtenés 0.30000000000000004 en lugar de un 0.3 limpio. Probalo vos mismo:
# Python
>>> 0.1 + 0.2
0.30000000000000004
>>> (0.1 + 0.2) == 0.3
False
# JavaScript
> 0.1 + 0.2
0.30000000000000004
> 0.1 + 0.2 === 0.3
false
⚠️ Ojo: nunca compares dos floats con==directamente. Usá una tolerancia (epsilon), por ejemploabs(a - b) < 1e-9. Para dinero, jamás uses float: usáDecimalen Python,BigDecimalen Java, o enteros que representen la unidad mínima (centavos).
Esto no es un capricho de Python o JavaScript. Pasa en C, Rust, Go, Java, Swift y cualquier lenguaje que use IEEE 754, que son prácticamente todos. La regla de oro: tratá a los floats como aproximaciones, no como números exactos. La aritmética binaria de coma flotante no es asociativa ni distributiva, y aceptarlo desde el inicio te ahorra horas de debugging.
Precisiones IEEE 754: half, float, double y la era del FP8
El mundo del IEEE 754 ofrece varios formatos según el balance que necesités entre precisión, rango y memoria. Cada formato es un trade-off explícito:
- half (binary16) — 16 bits totales: 5 de exponente, 10 de mantisa. ~3 dígitos decimales. Uso típico: gráficos, inferencia ML.
- float (binary32) — 32 bits: 8 exponente, 23 mantisa. ~7 dígitos. Cálculo general, gráficos 3D, juegos.
- double (binary64) — 64 bits: 11 exponente, 52 mantisa. ~15-17 dígitos. Default de Python y JavaScript, simulación científica.
- quad (binary128) — 128 bits: 15 exponente, 112 mantisa. ~33 dígitos. Física teórica, criptografía, casos donde double no alcanza.
En 2026 la verdadera revolución viene de los formatos sub-IEEE: FP8 (estandarizado por NVIDIA, ARM e Intel en OFP8), FP4 e incluso FP2. Estos formatos no son IEEE 754 estricto, pero comparten su filosofía y permiten entrenar e inferir LLMs como GPT-5.4 y Claude 4.7 con una fracción de la memoria y energía.
El FP8 viene en dos sabores definidos por OFP8: E4M3 (4 bits exponente, 3 bits mantisa, 1 bit signo) y E5M2 (5 bits exponente, 2 bits mantisa, 1 bit signo). E4M3 ofrece más precisión pero menor rango y se usa en el forward pass del entrenamiento; E5M2 sacrifica precisión por rango y se aplica a los gradientes durante el backward pass. Las GPUs NVIDIA H100 y posteriores tienen tensor cores nativos que aceleran estas operaciones hasta 4× respecto a FP16, con un consumo energético que cae proporcionalmente.
💭 Clave: entrenar Llama 3 405B en FP4 cabe en una sola H100 de 80GB; en FP16 necesitarías al menos 5 GPUs. La cuantización extrema es la diferencia entre tener acceso a un modelo o no tenerlo.
Errores comunes con floating point en producción
Los gotchas más típicos que vemos en bugs de producción cuando se usan floating point:
- Comparar floats con ==. Nunca lo hagas. Usá
abs(a - b) < epsilon. - Sumar muchos floats pequeños con uno grande. Pierde precisión catastróficamente. Usá Kahan summation o sumá ordenados de menor a mayor magnitud.
- Usar float para dinero. Error grave en sistemas financieros. Usá tipos decimales o enteros en centavos.
- Asumir asociatividad.
(a + b) + cno siempre es igual aa + (b + c)en floating point. - Asumir que sqrt(x*x) == x. Para valores grandes,
x*xpuede desbordar a infinito (overflow).
Para dinero la solución correcta usa Decimal en Python:
from decimal import Decimal, getcontext
getcontext().prec = 28
precio = Decimal('0.1') + Decimal('0.2')
print(precio) # 0.3 exacto, sin error
total = Decimal('19.99') * 3
print(total) # 59.97 exacto
El futuro: FP4, microscaling y los posits
La frontera actual del floating point va hacia formatos cada vez más comprimidos. NVIDIA introdujo FP4 nativo en su arquitectura Blackwell (2024) y en 2026 ya es estándar para inferencia de LLMs cuantizados. Microsoft, NVIDIA, AMD e Intel publicaron en conjunto los Microscaling Formats (MX), que combinan FP4 o FP8 con un factor de escala compartido por bloque, recuperando rango efectivo sin pagar el costo de bits adicionales por cada número.
El trade-off es claro: menos precisión por número, pero modelos que caben en menos VRAM y entrenan más rápido. La precisión perdida se compensa con técnicas como quantization-aware training (QAT), que enseña a la red a tolerar el ruido del formato comprimido durante el propio entrenamiento, y con calibración post-training en formatos como GPTQ y AWQ.
Mientras tanto, el comité IEEE sigue evolucionando el estándar. Se discute un binary8 oficial dentro de IEEE 754, y existe trabajo activo en posits, un competidor planteado por John Gustafson que usa codificación variable de exponente y mantisa para concentrar precisión cerca del 1.0 y degradarla suavemente lejos del cero. Por ahora siguen siendo experimentales, pero muestran que cuatro décadas después, la teoría de cómo representamos números reales en binario sigue siendo terreno fértil.
📖 Resumen en Telegram: Ver resumen
Preguntas frecuentes
¿Por qué los lenguajes no usan otra forma de representar decimales por defecto?
Porque IEEE 754 está implementado directamente en hardware. Cualquier alternativa —Decimal, BigDecimal, racionales— es órdenes de magnitud más lenta porque corre en software. Para cálculo masivo (gráficos, ML, simulación), el rendimiento gana sobre la precisión exacta.
¿Es seguro usar double para representar dinero?
No. Aunque double tiene 15-17 dígitos de precisión, los errores se acumulan en operaciones repetidas. Los sistemas financieros usan Decimal (Python), BigDecimal (Java), NUMERIC/DECIMAL en SQL, o enteros que representan la unidad mínima. Usar floats para dinero es una de las causas más comunes de bugs en producción.
¿Qué es un NaN y cuándo aparece?
NaN (Not a Number) es un valor especial definido por IEEE 754 que aparece en operaciones indefinidas como 0/0, sqrt(-1) o ∞ - ∞. NaN tiene una propiedad peculiar: NaN != NaN siempre devuelve true. Para detectarlo usá isnan() en C/Python o Number.isNaN() en JavaScript.
¿Qué diferencia hay entre underflow y overflow?
Overflow ocurre cuando un número excede el rango representable y se convierte en ±infinito (por ejemplo, 1e308 * 10 en double). Underflow pasa cuando un número es tan pequeño que no se puede representar y se redondea a 0 o a un valor denormal (subnormal). Ambos pueden causar bugs sutiles si no se manejan con cuidado.
¿Vale la pena aprender FP8/FP4 si no trabajo en machine learning?
Si trabajás en backend tradicional o frontend, probablemente no. Pero si tocás GPUs, embeddings, vectores, motores de búsqueda semántica o cualquier sistema con LLMs, entender estos formatos te permite optimizar memoria y latencia significativamente. La cuantización es ya parte estándar del stack de ML en producción.
¿Por qué Python muestra 0.1 como 0.1 si internamente es 0.10000000000000000555?
Porque desde Python 3.1, repr() usa una variante moderna del algoritmo de Steele y White que muestra el string decimal más corto que round-trip al mismo float. Como '0.1' es el string más corto que se redondea a esa representación binaria específica, Python lo muestra así. Pero internamente sigue siendo la versión truncada de 17 dígitos.
Referencias
- Bartosz Ciechanowski — Exposing Floating Point — explicación visual interactiva del IEEE 754, fuente de inspiración de este artículo.
- Wikipedia — IEEE 754 — resumen completo del estándar, sus revisiones (1985, 2008, 2019) y todos los formatos definidos.
- Python docs — Floating Point Arithmetic: Issues and Limitations — tutorial oficial sobre por qué Python (y todos los lenguajes IEEE 754) se comportan así.
- float.exposed — herramienta web del propio Ciechanowski para inspeccionar floating point bit a bit en tiempo real.
📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.
0 Comentarios