🧮 Computational Mathematics

Inicio · Parte 01 — Aritmética computacional y representación numérica

029 — Por qué 0.1 + 0.2 no es exactamente 0.3

basico-computacional clase 9 de 20 4 horas demostración why_point_one

0.1 no es representable en binario, igual que 1/3 no lo es en decimal; la desigualdad no es un fallo sino una consecuencia.

Fórmulas

0.1₁₀ = 0.0001100110011...₂  (periódico infinito)
comparación correcta: math.isclose(a, b, rel_tol=...)

Desarrollo

La clase 004 estableció que una fracción tiene desarrollo finito solo si su denominador reducido se factoriza en los primos de la base. En base 10 los primos son 2 y 5, y por eso 1/10 es finito. En base 2 el único primo es 2, así que 1/10 —cuyo denominador contiene un 5— tiene desarrollo binario periódico infinito.

Como la mantisa tiene 53 bits, ese desarrollo se trunca. El float más cercano a 0.1 es en realidad 0.1000000000000000055511151231257827021181583404541015625. Sumar dos aproximaciones y compararlas con la aproximación de 0.3 no tiene por qué dar igualdad, y de hecho no la da: la diferencia es de unos 5.5·10⁻¹⁷.

La conclusión correcta no es «los floats están rotos». Es que el operador == no es la comparación adecuada para resultados de cálculo en punto flotante. La comparación correcta declara una tolerancia: math.isclose(a, b, rel_tol=1e-12) pregunta si los dos números coinciden dentro de una precisión relativa declarada, que es la pregunta que realmente se quiere responder.

Hay una excepción importante: comparar con == sí es correcto cuando los valores son exactamente representables y no han pasado por operaciones inexactas —enteros pequeños, potencias de 2, resultados de asignaciones directas—. La regla práctica es preguntarse si el valor viene de un cálculo; si viene, hace falta tolerancia.

Ejemplo trabajado

La desigualdad y su explicación.

0.1 + 0.2 = 0.30000000000000004
0.3       = 0.29999999999999998889776975374843...
¿iguales con ==?           No
diferencia                 5.55e−17

0.1 con 50 decimales:
  0.10000000000000000555111512312578270211815834045410

Comparación correcta:
  math.isclose(0.1 + 0.2, 0.3, rel_tol=1e-12)  →  True

El error es de 5.5·10⁻¹⁷ sobre 0.3: precisión relativa de 1.8·10⁻¹⁶, exactamente el epsilon de máquina. El resultado es tan bueno como el formato permite.

Qué calcula el laboratorio

0.1 + 0.2 != 0.3 explicado con la fracción binaria real.

python classes/part-01-aritmetica-computacional-y-representacion-numerica/029-por-que-0-1-0-2-no-es-exactamente-0-3/lab.py
compmath run 029

Salidas del laboratorio (7)

Muestra de la ejecución real

{
  "0.1+0.2": 0.30000000000000004,
  "0.3": 0.3,
  "iguales": false,
  "diferencia": 5.551115123125783e-17,
  "0.1_como_fraccion_exacta": "3602879701896397/36028797018963968",
  "0.1_con_50_digitos": "0.10000000000000000555111512312578270211815834045410"
}

Errores comunes

Dónde se usa

Cualquier test numérico, criterio de convergencia o comparación de resultados. Los asserts de todo el programa usan tolerancia declarada precisamente por esto.

Idea rectora de la parte

La cancelación catastrófica destruye dígitos significativos sin lanzar excepciones.

Error a evitar

Usar float para dinero en vez de Decimal o enteros de centavos.

Conexión con IA

float32, bfloat16 y la cuantización a int8 son decisiones de representación. Los NaN en un entrenamiento casi siempre nacen aquí, no en la arquitectura.

Bibliografía de la clase

Archivos de la clase