Fundamentos · Tema 05

Propagación del error y cancelación

Cómo pequeñas imprecisiones viajan por un algoritmo, cuándo se amplifican y qué reformulaciones ayudan a conservar información significativa.

01 · Punto de partida

Los errores no permanecen aislados

Los datos de entrada pueden contener error de medición, los números se almacenan con precisión finita y cada operación puede redondear su resultado. Cuando una salida se utiliza como entrada del paso siguiente, esas pequeñas diferencias recorren el algoritmo.

Consideremos dos mediciones: a = 100.0 ± 0.1 y b = 99.9 ± 0.1. La diferencia calculada es a − b = 0.1. Sin embargo, los valores verdaderos compatibles con las mediciones permiten diferencias desde −0.1 hasta 0.3. El resultado es pequeño, pero su incertidumbre no lo es.

Para diseñar métodos confiables necesitamos responder:

  • ¿Cómo cambia el error al sumar, restar, multiplicar o dividir?
  • ¿Qué operaciones eliminan cifras significativas?
  • ¿Podemos escribir una fórmula equivalente que se comporte mejor?
  • ¿Cómo acumulamos muchos valores sin perder aportes pequeños?

02 · Representar una perturbación

Valor verdadero, aproximación y error

Podemos representar una entrada aproximada como el valor verdadero más una perturbación:

x̃ = x + Δx

El símbolo Δx describe aquí un error con signo. Si una función recibe x̃ en lugar de x, la salida cambia:

Δf = f(x + Δx) − f(x)

El objetivo de la propagación de errores es relacionar Δf con los errores de entrada. Algunas operaciones permiten obtener cotas sencillas; en otras utilizaremos aproximaciones locales.

Error de entrada
Incertidumbre o aproximación presente antes de ejecutar el cálculo.
Error propagado
Parte del error de salida causada por las entradas inexactas.
Error de cálculo
Error adicional introducido por truncamiento, redondeo e implementación.
Error total
Efecto combinado de todas las fuentes sobre el resultado final.

03 · Operaciones aditivas

Propagación en sumas y restas

Sean x̃ = x + Δx e ỹ = y + Δy. Al sumar:

(x + Δx) + (y + Δy) = (x + y) + (Δx + Δy)

Y al restar:

(x + Δx) − (y + Δy) = (x − y) + (Δx − Δy)

Si solo conocemos cotas absolutas |Δx| ≤ εx y |Δy| ≤ εy, una cota segura para ambas operaciones es:

|Δ(x ± y)| ≤ εx + εy

Ejemplo con intervalos

Si x = 20.0 ± 0.2 e y = 5.0 ± 0.1, entonces:

  • x + y = 25.0 ± 0.3 como cota conservadora;
  • x − y = 15.0 ± 0.3 como cota conservadora.

La cota suma los peores casos. Si los errores son aleatorios e independientes pueden utilizarse modelos estadísticos menos conservadores, pero eso requiere supuestos adicionales.

04 · Operaciones multiplicativas

Propagación en productos y cocientes

Para errores pequeños, los errores relativos de un producto se combinan aproximadamente mediante una suma:

Er(xy) ≈ Er(x) + Er(y)

En un cociente ocurre algo similar, siempre que el denominador no sea cero ni esté demasiado cerca de cero:

Er(x/y) ≈ Er(x) + Er(y)

La suma ofrece una cota aproximada de peor caso. Si x = 10 ± 1 % e y = 4 ± 2 %, el producto 40 puede tener aproximadamente hasta un 3 % de error relativo.

Reglas de primer orden para errores pequeños e independientes de la fórmula.
OperaciónMedida útilRegla aproximada o cota
x ± yError absolutoEa ≤ Ea(x) + Ea(y)
x × yError relativoEr ≈ Er(x) + Er(y)
x / yError relativoEr ≈ Er(x) + Er(y)
xⁿError relativoEr(xⁿ) ≈ |n| Er(x)

05 · Sensibilidad local

Propagación a través de una función

Si f es derivable y Δx es pequeño, la derivada aproxima el cambio de salida:

Δf ≈ f′(x) Δx

En magnitud:

|Δf| ≈ |f′(x)| |Δx|

Una derivada grande indica que una pequeña perturbación de entrada puede producir una diferencia de salida considerable.

Ejemplo: área de un círculo

Para A(r) = πr², la derivada es A′(r) = 2πr. Si medimos r = 10 cm con un error máximo de 0.01 cm:

|ΔA| ≈ 2π × 10 × 0.01 ≈ 0.628 cm²
function estimarErrorSalida(derivada, x, errorEntrada) {
  return Math.abs(derivada(x)) * Math.abs(errorEntrada);
}

const derivadaArea = radio => 2 * Math.PI * radio;
console.log(estimarErrorSalida(derivadaArea, 10, 0.01));

En el tema siguiente distinguiremos formalmente la sensibilidad propia del problema de la estabilidad del algoritmo que utilizamos para resolverlo.

06 · Pérdida de cifras

Cancelación al restar números cercanos

La cancelación ocurre al restar dos números casi iguales. Sus primeras cifras coinciden y desaparecen, dejando un resultado que depende de las últimas cifras, precisamente las menos confiables.

Supongamos que almacenamos cuatro cifras significativas:

1.234 − 1.233 = 0.001

Si ambos valores tenían errores de apenas ±0.0005, el error de la diferencia puede ser del mismo orden que el resultado. El error absoluto no necesariamente aumentó, pero el error relativo puede volverse enorme porque la salida es muy pequeña.

const a = 1.000000000000001;
const b = 1.000000000000000;
const diferencia = a - b;

console.log(diferencia);
console.log(diferencia.toPrecision(17));

07 · Reformulación algebraica

Evitar una resta peligrosa

Para x grande, la expresión siguiente resta dos raíces casi iguales:

√(x + 1) − √x

Multiplicando por el conjugado obtenemos una forma algebraicamente equivalente:

√(x + 1) − √x = 1 / (√(x + 1) + √x)

La segunda forma reemplaza la resta de números cercanos por una suma y una división.

function diferenciaDirecta(x) {
  return Math.sqrt(x + 1) - Math.sqrt(x);
}

function diferenciaEstable(x) {
  return 1 / (Math.sqrt(x + 1) + Math.sqrt(x));
}

for (const x of [1, 1e8, 1e12, 1e16]) {
  console.log(x, {
    directa: diferenciaDirecta(x),
    estable: diferenciaEstable(x)
  });
}

Para x = 10¹⁶, x + 1 se representa como el mismo Number que x. La forma directa devuelve cero; la reformulada conserva un resultado cercano a 5×10⁻⁹.

08 · Caso clásico

Fórmula cuadrática estable

La fórmula habitual para las raíces de ax² + bx + c = 0 es:

x = (−b ± √(b² − 4ac)) / 2a

Si b² es mucho mayor que 4ac, una de las ramas resta cantidades casi iguales. Podemos calcular primero:

q = −½ (b + signo(b) √(b² − 4ac))

Y obtener las raíces mediante x₁ = q/a y x₂ = c/q. Así evitamos la resta peligrosa.

function raicesCuadraticasEstables(a, b, c) {
  const discriminante = b * b - 4 * a * c;
  if (discriminante < 0) return [];

  const raizDiscriminante = Math.sqrt(discriminante);
  const signoB = b >= 0 ? 1 : -1;
  const q = -0.5 * (b + signoB * raizDiscriminante);

  if (q === 0) return [0, 0];
  return [q / a, c / q];
}

console.log(raicesCuadraticasEstables(1, 1e8, 1));

El método devuelve una raíz cercana a −10⁸ y otra cercana a −10⁻⁸. La fórmula directa puede perder la raíz pequeña por cancelación.

09 · Acumular muchos valores

Orden de suma y escala

Cuando sumamos una cantidad pequeña a otra mucho mayor, el aporte puede quedar por debajo de la separación entre números representables:

console.log(1e16 + 1 === 1e16); // true

const a = (1e16 + -1e16) + 1;
const b = 1e16 + (-1e16 + 1);

console.log(a); // 1
console.log(b); // 0

La suma matemática es asociativa; la suma de punto flotante no lo es. Cuando todos los valores tienen el mismo signo, sumar primero los de menor magnitud suele conservar más información. Si hay signos diferentes, el problema es más complejo porque también puede aparecer cancelación.

Ordenar tiene un costo y no resuelve todos los casos. Para acumulaciones importantes existen algoritmos especializados.

10 · Suma compensada

Algoritmo de Kahan

La suma compensada de Kahan mantiene una variable adicional que intenta recuperar las partes pequeñas perdidas en cada operación.

function sumaKahan(valores) {
  let suma = 0;
  let compensacion = 0;

  for (const valor of valores) {
    const ajustado = valor - compensacion;
    const siguiente = suma + ajustado;
    compensacion = (siguiente - suma) - ajustado;
    suma = siguiente;
  }
  return suma;
}

const datos = Array(100_000).fill(0.1);
const sumaComun = datos.reduce((total, valor) => total + valor, 0);
const sumaCompensada = sumaKahan(datos);

console.log({ sumaComun, sumaCompensada, esperado: 10_000 });

Kahan mejora notablemente muchas acumulaciones con un costo pequeño, pero no es una solución universal. Variantes como Neumaier manejan mejor algunos órdenes con valores de signos y magnitudes muy diferentes.

11 · Extremos del rango

Desbordamiento, subdesbordamiento y escalado

Un resultado intermedio puede salir del rango representable aunque el resultado final sea finito. Por ejemplo, calcular directamente √(x² + y²) puede desbordar al elevar números grandes al cuadrado o perder los pequeños por subdesbordamiento.

const x = 1e200;
const y = 1e200;

console.log(Math.sqrt(x * x + y * y)); // Infinity
console.log(Math.hypot(x, y));         // aproximadamente 1.414e200

Una versión escalada divide primero por la mayor magnitud:

√(x² + y²) = m √((x/m)² + (y/m)²),   m = max(|x|, |y|)

Math.hypot utiliza estrategias estables de este tipo. Escalar variables es una herramienta general para mantener los intermedios dentro de un rango seguro.

12 · Diseño numérico

Estrategias para controlar la propagación

≡

Reformular

Buscar expresiones equivalentes que eviten restas peligrosas y extremos intermedios.

↕

Escalar

Normalizar magnitudes para trabajar en una región numérica segura.

Σ+

Compensar

Utilizar algoritmos de suma que registren información perdida.

✓

Validar

Comparar formas equivalentes, mayor precisión o soluciones conocidas.

log

Registrar

Observar magnitudes intermedias, residuos y advertencias numéricas.

±

Acotar

Propagar intervalos o cotas cuando la incertidumbre de entrada es conocida.

La estrategia comienza por reconocer dónde se pierde información. Agregar más decimales a la salida no corrige un algoritmo que ya canceló las cifras útiles.

13 · Exploración interactiva

Laboratorio: observar la cancelación

El experimento evalúa √(x + 1) − √x en forma directa y racionalizada para x = 10ᵉ. El gráfico muestra el error relativo de la forma directa respecto de la estable.

EXPERIMENTO 05

Resta de raíces cercanas

x = 10⁰ = 1

Los resultados numéricos aparecen debajo.
Forma directa0,4142135624
Forma estable0,4142135624
Error relativo0
¿x + 1 = x?No

Para esta magnitud, ambas formas producen resultados muy cercanos.

Preguntas para explorar

  1. ¿A partir de qué exponente comienza a crecer visiblemente el error?
  2. ¿En qué momento x + 1 se representa igual que x?
  3. ¿Por qué la forma estable puede devolver un valor distinto de cero en ese caso?
  4. ¿La expresión matemática cambió al racionalizar?

14 · Comprobar una implementación

Diagnóstico de problemas numéricos

Cuando un resultado parece incorrecto o cambia inesperadamente, conviene seguir un procedimiento:

  1. 1
    Inspeccionar escalas.

    Buscar entradas e intermedios que difieran por muchos órdenes de magnitud.

  2. 2
    Localizar restas.

    Identificar sustracciones de valores cercanos y divisiones por cantidades pequeñas.

  3. 3
    Comparar formulaciones.

    Evaluar una expresión algebraicamente equivalente o un algoritmo alternativo.

  4. 4
    Aumentar la precisión de referencia.

    Utilizar una biblioteca multiprecisión para diagnosticar, aunque la producción use Number.

  5. 5
    Probar casos límite.

    Incluir valores casi iguales, muy grandes, muy pequeños y combinaciones de signos.

function compararFormas(x) {
  const directa = Math.sqrt(x + 1) - Math.sqrt(x);
  const estable = 1 / (Math.sqrt(x + 1) + Math.sqrt(x));
  const diferencia = Math.abs(directa - estable);

  return {
    x,
    directa,
    estable,
    diferencia,
    incrementoPerdido: x + 1 === x
  };
}

console.table([1, 1e8, 1e12, 1e16].map(compararFormas));

15 · Comprensión

Confusiones frecuentes

«La resta siempre aumenta el error absoluto»

No necesariamente. El problema típico es que el resultado se vuelve pequeño mientras el error absoluto permanece, por lo que el error relativo aumenta.

«Expresiones equivalentes deben producir los mismos bits»

La equivalencia algebraica supone aritmética exacta. En punto flotante, los resultados intermedios se redondean y el orden de operaciones importa.

«Usar Number.EPSILON evita la cancelación»

Agregar una constante no recupera cifras perdidas y puede modificar el problema. La solución suele ser reformular o utilizar mayor precisión.

«Kahan vuelve exacta cualquier suma»

Reduce el error en muchas acumulaciones, pero sigue trabajando con precisión finita y tiene casos difíciles. Es una mejora algorítmica, no aritmética exacta.

«Infinity solo aparece cuando el resultado final es enorme»

También puede aparecer en un intermedio, como x*x, aunque una forma escalada del resultado final sea finita.

16 · Práctica guiada

Ejercicios

Ejercicio 1: suma con incertidumbre

Dos longitudes son 12.0 ± 0.1 cm y 8.0 ± 0.2 cm. Calculá una cota conservadora para el error absoluto de la suma y de la diferencia.

Ver solución

En ambos casos la cota es 0.1 + 0.2 = 0.3 cm. Podemos informar 20.0 ± 0.3 cm y 4.0 ± 0.3 cm. La diferencia tiene un error relativo mayor porque su valor es menor.

Ejercicio 2: producto

El largo de un rectángulo tiene un error relativo máximo de 1 % y el ancho uno de 2 %. Estimá el error relativo máximo de primer orden para el área.

Ver solución

Como el área es un producto, la cota relativa aproximada es 1 % + 2 % = 3 %.

Ejercicio 3: identificar cancelación

¿Cuál de estas expresiones es más vulnerable cuando x es pequeño: cos(x) − 1 o cos(x) + 1? Justificá.

Ver solución razonada

Cuando x es pequeño, cos(x) está cerca de 1. La expresión cos(x) − 1 resta números cercanos y puede perder cifras significativas. Una reformulación útil es −2 sin²(x/2).

Ejercicio 4: reformular

Implementá las dos formas de cos(x) − 1 y comparalas para x = 10⁻⁴, 10⁻⁸ y 10⁻¹².

Ver una posible solución
function cosenoMenosUnoDirecto(x) {
  return Math.cos(x) - 1;
}

function cosenoMenosUnoEstable(x) {
  return -2 * Math.sin(x / 2) ** 2;
}

for (const x of [1e-4, 1e-8, 1e-12]) {
  console.log(x, {
    directo: cosenoMenosUnoDirecto(x),
    estable: cosenoMenosUnoEstable(x)
  });
}

Ejercicio 5: suma precisa

Compará la suma común y la suma de Kahan para cien mil copias de 0.01. Medí el error absoluto respecto de 1000.

Ver una posible solución
const valores = Array(100_000).fill(0.01);
const comun = valores.reduce((suma, x) => suma + x, 0);
const compensada = sumaKahan(valores);

console.log({
  errorComun: Math.abs(comun - 1000),
  errorCompensado: Math.abs(compensada - 1000)
});

17 · Síntesis

Ideas para recordar

  • Los errores de entrada y de cálculo se propagan a través de cada operación.
  • En sumas y restas se combinan naturalmente errores absolutos; en productos y cocientes, errores relativos.
  • La derivada aproxima cuánto amplifica una función una perturbación pequeña.
  • Restar números cercanos puede destruir cifras significativas y aumentar el error relativo.
  • Una reformulación algebraica puede evitar cancelación sin cambiar el problema matemático.
  • El orden de suma importa en punto flotante y una suma compensada puede conservar aportes pequeños.
  • Escalar evita desbordamientos o subdesbordamientos innecesarios en resultados intermedios.
  • La estabilidad debe comprobarse con casos extremos y formulaciones de referencia.

En el próximo tema diferenciaremos condicionamiento, estabilidad y convergencia: tres conceptos relacionados, pero correspondientes al problema, al algoritmo y a la sucesión de aproximaciones.