Abre el navegador en el ordenador, pulsa F12 para abrir la consola, escribe 0.1 + 0.2 y pulsa Enter. La respuesta es 0.30000000000000004. Prueba en Python: lo mismo. Una máquina hecha para calcular falla una suma de primaria.
La pregunta tonta de esta lección es: ¿por qué el ordenador dice que 0,1 + 0,2 no es 0,3? No está roto. Guarda los números de una manera en la que 0,1 no cabe exacto. Esta lección explica cuál es esa manera, por qué casi nunca se nota y cuándo sí.
Elige tu carta: ♣ si empiezas de cero, ♦ si no te asusta una fórmula sencilla, ♠ si quieres la notación. Puedes cambiarla en cualquier momento.
0,1 no cabe en binario
Novato
Intenta escribir un tercio con decimales: 0,3333… Por muchas cifras que pongas, nunca llegas, siempre falta un poco. El número no tiene nada de raro. Lo que pasa es que en decimal, que cuenta en décimas, centésimas y milésimas, un tercio no tiene final.
El ordenador no cuenta en décimas: cuenta en mitades. Mitades, cuartos, octavos, dieciseisavos… Cada trozo es la mitad del anterior. Y con trozos así, una décima tampoco tiene final: te acercas muchísimo, pero siempre falta un trocito.
Como el ordenador solo tiene sitio para unas cuantas decenas de trozos, corta ahí y se queda con lo más cercano. Su 0,1 no es 0,1: es un pelín más. Su 0,2 también se pasa un pelín, y su 0,3 se queda un pelín corto. Al sumar, esos pelines no cuadran, y sale 0,30000000000000004.
Intermedio
El ordenador guarda los números en binario, solo con ceros y unos. En decimal, cada cifra después de la coma vale la décima parte que la anterior: décimas, centésimas… En binario, cada cifra vale la mitad que la anterior: ½, ¼, ⅛, 1/16…
Para escribir 0,1 en binario hay que ver qué trozos caben: ½ no, ¼ no, ⅛ no, 1/16 sí… Sale 0,0001100110011…, y el grupo 0011 se repite para siempre. Con nueve cifras llegas a 0,099609375; con más te acercas, pero nunca terminas. Le pasa lo mismo que a 1/3 en decimal.
Un ordenador normal guarda 53 cifras binarias y redondea al número más cercano que puede escribir con ellas. Su 0,1 vale exactamente 0,1000000000000000055511151231257827021181583404541015625, un poco más que 0,1 (tutorial de Python).
Con 0,2 y 0,3 pasa lo mismo, cada uno con su pequeño error. La suma de los dos primeros no cae en el número que el ordenador usa para 0,3, sino en el siguiente: 0,30000000000000004.
Técnico
| En ♦ decíamos | En ♠ se escribe |
|---|---|
| binario | base 2; (·)₂ marca un número escrito en base 2 |
| el grupo 0011 se repite para siempre | 0,0(0011)₂ = Σₖ≥₁ (2⁻⁴ᵏ + 2⁻⁴ᵏ⁻¹), desarrollo periódico |
| Le pasa lo mismo que a 1/3 en decimal | p/q ∈ ℚ (racional) irreducible: desarrollo finito en base 2 ⇔ q = 2ᵏ |
| 53 cifras binarias | precisión p = 53 bits |
| redondea al número más cercano | fl(x): redondeo al más cercano, con los empates al par |
| La suma | x ⊕ y = fl(x + y), la suma en coma flotante |
| el siguiente | el vecino a un ulp (unit in the last place), aquí 2⁻⁵⁴ |
Una fracción irreducible p/q tiene desarrollo finito en base 2 solo si q es potencia de 2. 1/10 lleva un 5 en el denominador y queda periódico: 0,1 = 0,0(0011)₂ = Σₖ≥₁ (2⁻⁴ᵏ + 2⁻⁴ᵏ⁻¹). La serie geométrica lo confirma: (3/32) / (15/16) = 1/10.
Con p = 53, fl(0,1) es la fracción J/2ᴺ más cercana con J de 53 bits: J = 7205759403792794 y N = 56, que simplificada queda 3602879701896397 / 2⁵⁵ (tutorial de Python). Como 10 × 3602879701896397 = 36028797018963970 y 2⁵⁵ = 36028797018963968:
fl(0,1) − 0,1 = 2 / (10 · 2⁵⁵) = 1 / (5 · 2⁵⁵) ≈ 5,55×10⁻¹⁸
Se redondeó hacia arriba. En 0,1 ⊕ 0,2, la suma exacta fl(0,1) + fl(0,2) cae justo a medio camino entre dos números de 53 bits. La regla de empates al par la manda al de arriba, que está un ulp, 2⁻⁵⁴ ≈ 5,55×10⁻¹⁷, por encima de fl(0,3). Por eso 0.1 + 0.2 == 0.3 es falso en Python, en JavaScript y en cualquier lenguaje que use este formato (cálculo propio con aritmética exacta; en JavaScript, 0.1 + 0.2 - 0.3 da 2⁻⁵⁴).
El ordenador suma bien lo que tiene. El problema es que lo que tiene no es 0,1: no le cabe.
Coma flotante: notación científica en binario
Novato
En la lección de números enormes viste la notación científica: 7×10²⁷ es un 7 con 27 ceros detrás. Sirve para escribir números enormes y diminutos con pocas cifras.
El ordenador hace lo mismo, pero en binario y siempre con el mismo número de cifras: unas 16 cifras decimales. Se llama coma flotante porque la coma “flota”: se mueve según lo grande o lo pequeño que sea el número, y las cifras son siempre las mismas.
Imagina una regla con un número fijo de marcas. Cerca del cero, las marcas están muy juntas y puedes medir cosas pequeñas con mucho detalle. Lejos del cero, están cada vez más separadas. Si lo que mides cae entre dos marcas, apuntas la más cercana: eso es redondear.
Con 16 cifras, la regla es finísima: el 0,1 se desvía menos de una parte en diez mil billones. Por eso casi nunca se nota.
Intermedio
Un número en coma flotante se guarda en tres partes, como en notación científica pero en base 2:
- el signo, + o −;
- el exponente, que dice por qué potencia de 2 se multiplica;
- la mantisa, que son las cifras.
El formato normal se llama binary64 (o “doble precisión”): 64 bits, 1 de signo, 11 de exponente y 52 de mantisa (MDN; NumPy). La mantisa empieza siempre por un 1 que no hace falta guardar, así que tiene 53 cifras binarias, unas 16 decimales. Es el formato de los números de JavaScript, de los float de Python y de Excel. Por ejemplo, 0,1 = + 1,6 × 2⁻⁴.
Como hay siempre las mismas cifras, la separación entre dos números seguidos crece con el tamaño. Justo por encima de 1 es de 2,2×10⁻¹⁶. Junto a 10¹⁶ es de 2, y por eso 10¹⁶ + 1 da 10¹⁶. Por lo mismo, los números enteros solo son exactos hasta unos 9×10¹⁵ (MDN).
Algunas combinaciones de bits se reservan para el cero, el infinito y los resultados sin sentido, como la raíz cuadrada de −1 (Microsoft, sobre Excel).
Cuando el resultado de una operación cae entre dos números que se pueden guardar, se redondea al más cercano. Cada redondeo se equivoca, como mucho, en media separación: menos de una parte en 9×10¹⁵.
Técnico
| En ♦ decíamos | En ♠ se escribe |
|---|---|
| signo | s ∈ {0, 1} |
| exponente | E, de 11 bits, con sesgo 1023: el exponente real es E − 1023 |
| mantisa | (1.f)₂, con f los 52 bits guardados y el 1 implícito |
| binary64 | formato de IEEE 754-2019 con p = 53, emin = −1022 y emax = 1023 |
| la separación entre dos números seguidos | ulp(x) = 2^(⌊log₂|x|⌋ − 52), con ⌊·⌋ la parte entera por abajo |
| se redondea al más cercano | fl(x) = x(1 + δ), |δ| ≤ u = 2⁻⁵³ (u: unidad de redondeo) |
| Justo por encima de 1 es de 2,2×10⁻¹⁶ | ε = ulp(1) = 2⁻⁵² (Number.EPSILON, sys.float_info.epsilon) |
| el infinito y los resultados sin sentido | ±∞ y NaN (not a number), con E = 2047; E = 0, para el cero y los subnormales |
Un binary64 normal vale x = (−1)ˢ · (1.f)₂ · 2^(E − 1023), con 1 ≤ E ≤ 2046. Los valores E = 0 y E = 2047 se reservan para el cero, los subnormales (sin el 1 implícito), ±∞ y NaN. Los parámetros p = 53, emin = −1022 y emax = 1023 son los de la tabla D-1 de Goldberg (1991); la revisión actual del estándar es IEEE 754-2019.
Entre 2ᵉ y 2ᵉ⁺¹ hay 2⁵² números igualmente espaciados, separados ulp(x) = 2^(⌊log₂|x|⌋ − 52). Con redondeo al más cercano, fl(x) = x(1 + δ) con |δ| ≤ u = 2⁻⁵³, y cada operación básica cumple lo mismo: x ⊕ y = (x + y)(1 + δ) (Goldberg).
Cuidado con los nombres. MDN, Python y PyTorch llaman épsilon a ε = 2⁻⁵² = ulp(1), la distancia de 1 al siguiente número. Goldberg llama machine epsilon a la cota del error relativo, que es la mitad: u = 2⁻⁵³. Los enteros son exactos hasta 2⁵³ = 9 007 199 254 740 992; desde ahí, ulp ≥ 2 (MDN, Number.MAX_SAFE_INTEGER = 2⁵³ − 1).
El redondeo se acumula
Novato
Imagina una báscula de camiones un poco especial: no vuelve a pesarlo todo, sino que suma lo que subes al número que ya marca y redondea al kilo. Encima hay un camión de 10 000 kilos, y tienes mil plumas que juntas pesan un kilo. Subes una pluma: la báscula sigue en 10 000. Otra: 10 000. Las mil, una a una: 10 000, porque cada vez redondea al kilo y una pluma sola no llega. Ahora mete las mil plumas en una bolsa y súbela de golpe: 10 001.
Al ordenador le pasa lo mismo. Cada suma redondea, y los errores pequeños se acumulan o se pierden según el orden en que sumes. Si sumas 0,1 diez veces seguidas, no da 1: da 0,9999999999999999.
Casi siempre da igual, porque el error es diminuto. Pero si comparas el resultado con 1 para decidir algo, la respuesta es “no son iguales”. Por eso, con números con decimales, los programadores no preguntan “¿son iguales?”, sino “¿están muy cerca?”.
Intermedio
Cada operación redondea, y los errores se van acumulando. Al sumar 0,1 en un bucle, en JavaScript:
| Veces | Resultado |
|---|---|
| 3 | 0,30000000000000004 |
| 8 | 0,7999999999999999 |
| 10 | 0,9999999999999999 |
| 10 000 | 1000,0000000001588 |
Además, el orden cambia el resultado. Sumar un número pequeño a uno muy grande puede no cambiar nada (10¹⁶ + 1 = 10¹⁶), así que (a + b) + c no siempre da lo mismo que a + (b + c) (Goldberg, 1991).
Tres remedios habituales:
- Comparar con tolerancia, en vez de con
==. En Python,math.isclose(0.1 + 0.1 + 0.1, 0.3)daTrue(tutorial de Python). - Sumar con cuidado.
math.fsumlleva la cuenta de las cifras que se pierden y redondea una sola vez. Y desde Python 3.12,sum()compensa los errores por dentro:sum([0.1] * 10) == 1.0daTrueen casi todas las instalaciones, mientras que el bucle a mano da 0,9999999999999999. - Redondear al mostrar, no al calcular: dos decimales para un precio.
Técnico
| En ♦ decíamos | En ♠ se escribe |
|---|---|
| Cada operación redondea | x ⊕ y = fl(x + y), y x ⊖ y = fl(x − y) |
| los errores se van acumulando | suma recursiva s ← s ⊕ xⱼ: el sumando j se perturba hasta (n − j)u |
| el orden cambia el resultado | ⊕ es conmutativa pero no asociativa |
| Comparar con tolerancia | |a − b| ≤ max(rel_tol · max(|a|, |b|), abs_tol), con rel_tol = 10⁻⁹ (math.isclose) |
| Sumar con cuidado | suma compensada de Kahan, con c el error que arrastra cada paso |
| compensa los errores por dentro | suma de Neumaier, una variante de la de Kahan (gh-100425) |
| redondea una sola vez | un único redondeo al final (math.fsum); O(nu²): términos del orden de nu² o menores |
En la suma recursiva, cada sumando xⱼ entra con un factor (1 + δⱼ), con |δⱼ| < (n − j)u: el error crece con n (Goldberg, 1991, que llama épsilon a u = 2⁻⁵³, como se vio en el bloque anterior). La suma compensada de Kahan guarda en c lo que ha perdido cada ⊕ y lo devuelve en el paso siguiente. Su error queda en (2u + O(nu²)) Σ|xⱼ|, casi sin depender de n (Goldberg, teorema 8; Kahan, CACM 8, 40, 1965).
Sin asociatividad, el orden importa: (10³⁰ ⊕ −10³⁰) ⊕ 1 = 1, pero 10³⁰ ⊕ (−10³⁰ ⊕ 1) = 0 (Goldberg). Una consecuencia: repartir una suma larga entre varios procesadores cambia el orden, y el resultado puede variar en las últimas cifras.
Para comparar, math.isclose(a, b) comprueba |a − b| ≤ max(rel_tol · max(|a|, |b|), abs_tol), con rel_tol = 10⁻⁹ por defecto: unas 9 cifras de acuerdo (documentación de Python). sum() usa la suma de Neumaier desde Python 3.12 (gh-100425) y math.fsum hace un solo redondeo al final.
Nunca preguntes a un número con decimales si es igual a otro. Pregúntale si está muy cerca.
Menos bits para la IA, decimales para el dinero
Novato
No todos los números necesitan 16 cifras. Una red neuronal, como las que hay detrás de un traductor automático, hace muchísimas multiplicaciones con millones de números. Si cada número ocupa la mitad, caben el doble en memoria y las cuentas pueden ir más rápido. A cambio, cada número es más basto, como una foto con menos píxeles.
Con 16 bits, el ordenador solo tiene unas 3 cifras. Si sumas 0,1 una y otra vez, al llegar a 256 la suma se queda atascada: 0,1 es menos que media separación entre dos marcas de la regla, y se redondea a nada. Lo verás en el widget de abajo.
Con el dinero pasa al revés: no queremos ni un céntimo de error. Para el dinero se recomienda no usar estos números con decimales en binario, sino contar en céntimos, con números enteros, que se guardan exactos, o usar cuentas en decimal, como hacemos las personas.
Intermedio
Además de binary64 hay formatos más pequeños:
- float32, 32 bits (1 + 8 + 23): unas 7 cifras.
- float16, 16 bits (1 + 5 + 10): unas 3 cifras, y nada por encima de 65 504.
- bfloat16, 16 bits (1 + 8 + 7): solo unas 2 cifras, pero el mismo rango que float32. Lo pensaron en Google para el aprendizaje automático (blog de Google Cloud, 2019).
Las redes neuronales usan mucho estos formatos pequeños: ocupan la mitad de memoria y pueden ir más rápido. El truco se llama precisión mixta: hacer las multiplicaciones en 16 bits y guardar una copia de los pesos de la red en 32. En float16, un cambio pequeño puede perderse si el número al que se suma es 2048 veces mayor o más (Micikevicius et al., 2018). Es la báscula de camiones otra vez.
Para el dinero se hace lo contrario: aritmética decimal, que cuenta en décimas y centésimas como nosotros, o enteros de céntimos. En decimal, 0,1 + 0,1 + 0,1 − 0,3 da exactamente 0; en binario, 5,55×10⁻¹⁷ (documentación del módulo decimal de Python, pensado para contabilidad).
Técnico
| En ♦ decíamos | En ♠ se escribe |
|---|---|
| unas 7 cifras | p · log₁₀2 cifras decimales: 7,22 con p = 24 |
| nada por encima de 65 504 | máximo finito (2 − 2¹⁻ᵖ) · 2^emax, con emax el mayor exponente |
| el mismo rango que float32 | el mismo exponente de 8 bits: emax = 127 |
| precisión mixta | mixed precision: productos en FP16 o BF16 (float16, bfloat16); pesos maestros y acumulación en FP32 (float32) |
| los pesos de la red | pesos w; gradiente: cuánto cambia la pérdida (el error de la red) al mover cada peso |
| TPU (Tensor Processing Unit): el chip de Google para redes neuronales | |
| un cambio pequeño puede perderse | w ⊕ Δw = w si |Δw| < ulp(w)/2 |
| 2048 veces mayor | 2¹¹ = 2ᵖ con p = 11 |
| aritmética decimal | coma flotante en base β = 10 (módulo decimal de Python) |
Con p bits de precisión y exponente máximo emax, ε = 2¹⁻ᵖ y el máximo finito es (2 − 2¹⁻ᵖ) · 2^emax. Los bits salen de NumPy, PyTorch y Google; los valores de la tabla son cálculo propio. Para binary16, (2 − 2⁻¹⁰) · 2¹⁵ = 65 504, el mismo valor que dan Micikevicius et al. (2018).
w ⊕ Δw = w en cuanto |Δw| < ulp(w)/2, y en FP16 eso puede pasar desde |w| ≥ 2¹¹|Δw| (Micikevicius et al.). Su receta de precisión mixta guarda los pesos maestros en FP32, multiplica en FP16 y escala la pérdida para que los gradientes pequeños no caigan a cero: en FP16 no hay ningún número positivo por debajo de 2⁻²⁴, y en su detector de objetos (SSD) bastó con un factor 8. En las TPU de Google, cada multiplicación de matrices multiplica en bfloat16 y acumula en FP32 (Google Cloud, 2019). El widget lo enseña con 0,1: en float16, desde 256 el ulp es 0,25 y 0,1 < 0,125.
El módulo decimal de Python es coma flotante en base 10: 0,1 se guarda exacto y 0,1 + 0,1 + 0,1 − 0,3 da 0. Para dinero también valen los enteros de céntimos, exactos en binary64 hasta 2⁵³.
Prueba tú: suma 0,1 una y otra vez
Elige cómo guarda los números el ordenador y cuántas veces suma 0,1. Compara lo que da con lo que debería dar y mira cómo crece el error en la gráfica. Prueba float16 con 5000 veces.
Reto final
En tres frases
- El ordenador cuenta en binario, y en binario 0,1 no tiene final, como 1/3 en decimal: se guarda redondeado, un pelín por encima.
- La coma flotante es notación científica en binario con cifras fijas (unas 16 en binary64): cada operación redondea, el error se acumula y el orden de las sumas importa.
- Por eso los números con decimales se comparan con tolerancia; la IA usa formatos de 16 bits porque ahorran memoria, y el dinero va en céntimos enteros o en decimal.
Fuentes
- Por qué 0,1 no es exacto, su valor guardado,
math.iscloseymath.fsum: Python 3.15, tutorial, capítulo 15 y módulomath.sum()con suma de Neumaier: novedades de Python 3.12. Aritmética decimal: módulodecimal. - Representación, redondeo, no asociatividad y suma de Kahan: D. Goldberg, “What every computer scientist should know about floating-point arithmetic”, ACM Computing Surveys 23(1), 5–48 (1991), doi:10.1145/103162.103163, reimpresión de Oracle; W. Kahan, Communications of the ACM 8(1), 40 (1965), doi:10.1145/363707.363723. Estándar: IEEE 754-2019.
- Formatos y bits: MDN,
Number; NumPy 2.5, tipos de datos; PyTorch 2.14,torch.dtype; Excel y la coma flotante (Microsoft). - bfloat16: blog de Google Cloud (2019) y documentación de Cloud TPU. Precisión mixta: P. Micikevicius et al., “Mixed Precision Training”, ICLR 2018, arXiv:1710.03740.
Cómo está hecha esta lección: investigada y redactada con ayuda de Claude a partir de las fuentes de arriba, comprobadas y contrastadas con una segunda fuente el 15 de octubre de 2026. Los resultados de sumar 0,1 en un bucle, en cada formato, se midieron en JavaScript (Node 24) y los comprueba un test; las cifras sin enlace propio son cálculos a partir de las fuentes. Los dibujos son propios.