ELAI S.r.l.

Una varianza negativa: por qué una fórmula correcta puede fallar en el ordenador

Tres números, varianza exacta 14/9 y un resultado de −128: derivación, precisión finita y comparación reproducible de algoritmos.

Una varianza negativa: por qué una fórmula correcta puede fallar en el ordenador

Cuando un número imposible revela un problema real

Una pipeline de IA mide la variación antes de normalizar datos o comprobar un sensor. Los valores son grandes, pero sus diferencias pequeñas. El programa devuelve una varianza negativa. Como la varianza promedia cuadrados, ese signo no puede describir la cantidad matemática esperada. ¿Fallan los datos, la fórmula o la ejecución de la aritmética? Aislamos la tercera posibilidad con tres números sintéticos.

La pregunta es precisa: ¿son intercambiables en precisión finita dos expresiones equivalentes sobre números reales? Veremos por qué no, compararemos un cálculo centrado en dos pasadas con una actualización incremental y distinguiremos error aritmético de información ya perdida en la entrada. No medimos una línea productiva ni la precisión de un modelo EL-AI. Las unidades son genéricas: entradas en voltios darían varianza en voltios cuadrados, sin datos de un voltímetro real.

Definir las desviaciones antes de elegir el algoritmo

La media aritmética es el punto de equilibrio de los valores. Una desviación es un valor menos esa media. La varianza describe la desviación cuadrática media; elevar al cuadrado evita que diferencias positivas y negativas se cancelen. n es el número de observaciones, x_i el valor i-ésimo y mu la media. Dividimos por n: calculamos la varianza poblacional de los tres valores que forman nuestra población finita.

mu = (1/n) sum_i x_i\nv = (1/n) sum_i (x_i - mu)^2\n = (1/n) sum_i x_i^2 - mu^2

La última igualdad resulta de desarrollar los cuadrados. La suma de −2 mu x_i es −2 n mu² y mu² aparece n veces. Al dividir por n queda la media de cuadrados menos el cuadrado de la media. En aritmética exacta el resultado coincide y no puede ser negativo. La última expresión parece cómoda porque solo acumula sumas y cuadrados. Pero comodidad algebraica no garantiza estabilidad numérica.

La corrección n−1 responde a otra pregunta estadística: estimar la varianza de una población mayor desde una muestra, bajo supuestos adecuados. No repara la cancelación numérica. Con tres valores cambiar el denominador multiplica la varianza por 3/2 sin recuperar cifras perdidas. Las llamadas a NumPy usan explícitamente ddof=0 para comparar la misma cantidad matemática.

El ejemplo resuelto incluso sin ordenador

Tomemos b=1.000.000.000 y los valores b+1, b+2, b+4. La media es b+7/3. Las desviaciones son −4/3, −1/3 y 5/3; sus cuadrados suman 42/9 y dividir por tres da 14/9. La base grande desaparece porque una traslación común conserva las distancias a la media. La varianza exacta es aproximadamente 1,5555555556: ni cero ni negativa.

El programa ejecutado con Python 3.14.0, float de 53 bits de significando y acumulación secuencial devuelve −128 con la fórmula directa. La búsqueda determinista usa bases 10^k para k de 0 a 16 e incrementos fijos 1, 2, 4. El primer resultado negativo aparece en k=9. Publicar el criterio evita presentar un caso seleccionado como frecuencia de error. No afirmamos que cualquier terna u ordenador produzca −128.

Dónde desaparecen las cifras

Un número de coma flotante conserva un número finito de cifras significativas, no un número fijo de decimales. Al crecer la magnitud crece la separación entre valores representables. Cerca de b, math.ulp(b) es aproximadamente 1,19×10⁻⁷, de modo que los incrementos enteros siguen siendo distinguibles. En k=9 el problema no es que 1, 2 y 4 desaparecieran al entrar: surge al elevar después los valores grandes al cuadrado.

La fórmula directa resta dos cantidades cercanas a 10¹⁸ para obtener un residuo próximo a 1,56. Los redondeos de productos, sumas y media pueden ser grandes respecto a ese residuo. La resta revela la incertidumbre: cancela cifras comunes sin recrear las que ya no estaban disponibles. No toda resta de números cercanos es intrínsecamente inexacta; los errores previos de los operandos dominan el pequeño resultado buscado.

Sustituir la acumulación por math.fsum es útil, pero no una cura universal. fsum mejora la suma de sus entradas; no vuelve exactos los productos x*x ya redondeados ni el cuadrado de la media. En el ejemplo también devuelve −128. Recortar mediante max(v,0) elimina el signo imposible devolviendo cero, mientras la varianza correcta sigue siendo 14/9. Puede ser una política de contención, nunca prueba de haber recuperado información.

Centrar antes de elevar al cuadrado

El método de dos pasadas estima primero la media y después suma las desviaciones cuadráticas respecto a ella. Las cantidades elevadas al cuadrado son pequeñas y no hay una resta final de términos enormes. Usamos fsum tanto para la media como para las desviaciones. Si el centro calculado es mu+e, en aritmética exacta la distancia cuadrática media es v+e² porque las desviaciones verdaderas suman cero. Un pequeño error del centro no se convierte automáticamente en gran error de varianza. Las otras operaciones siguen redondeando.

(1/n) sum_i (x_i - (mu+e))^2 = v + e^2

Para un flujo que no queremos releer, la actualización conocida como Welford mantiene contador, media actual y M2, la suma centrada de cuadrados. Al llegar x_k, delta es la distancia a la media anterior y esta se desplaza delta/k. Actualizamos M2 con delta por la distancia a la media nueva. La varianza final es M2/n, no M2/(n−1) en nuestra comparación.

delta = x_k - mu_(k-1) mu_k = mu_(k-1) + delta/k M2_k = M2_(k-1) + delta * (x_k - mu_k)

La recurrencia se verifica sin invocar una biblioteca. Desplazar el centro de los k−1 valores previos delta/k aumenta su contribución en (k−1)delta²/k², pues sus desviaciones anteriores suman cero. El nuevo valor queda a distancia delta(k−1)/k de la nueva media y añade delta²(k−1)²/k². Ambos incrementos suman delta²(k−1)/k, igual a delta(x_k−mu_k). La derivación es exacta sobre los reales; el programa no hereda precisión infinita.

Resultados y significado de la referencia exacta

MétodoVarianza, unidades²
Fórmula directa-128
Fórmula directa + fsum-128
Dos pasadas1.5555555555555571
Welford1.555555522441864
NumPy 2.5.3 float641.5555555555555571
Fracción exacta14/9

En este caso dos pasadas y NumPy coinciden; Welford queda muy cerca, pero no es idéntico al valor correcto. No es una competición universal entre bibliotecas. NumPy recibe float64 y ddof=0; los otros métodos son implementaciones breves del experimento. Fraction.from_float convierte cada entrada flotante real en una fracción exacta y calcula sin redondeo. En k=9 los enteros se representan exactamente, por lo que la referencia coincide también con los datos que queríamos generar.

Con bases mayores aparece una distinción fundamental. Cerca de 10¹⁶ los float adyacentes están separados por 2, de modo que el incremento 1 ya no siempre se conserva como pretendíamos. Ningún algoritmo de varianza puede recuperarlo tras la conversión. Por eso el gráfico mide el error respecto a la varianza exacta de las entradas realmente representadas, sin suponer 14/9 en todo el barrido. Un oráculo incorrecto mezclaría representación de entrada y estabilidad aritmética.

Izquierda: error absoluto respecto a entradas representadas para bases 10^k e incrementos 1, 2, 4; escala logarítmica simétrica con región lineal dentro de ±10⁻¹⁴ para mostrar cero. Derecha: k=9. Las líneas superpuestas no implican precisión universal.
Izquierda: error absoluto respecto a entradas representadas para bases 10^k e incrementos 1, 2, 4; escala logarítmica simétrica con región lineal dentro de ±10⁻¹⁴ para mostrar cero. Derecha: k=9. Las líneas superpuestas no implican precisión universal.

El programa comprueba también las seis permutaciones de la terna. En este entorno la fórmula directa permanece en −128 y Welford varía aproximadamente entre 1,5555555224 y 1,5555555820. Incluso algoritmos más estables pueden depender del orden en precisión finita. El barrido y las permutaciones son controles deterministas, no una prueba formal para cualquier secuencia. El JSON incluye todos los resultados, no solo casos favorables.

Elegir el método entendiendo los compromisos

Tanto dos pasadas como Welford requieren trabajo proporcional a n. Welford mantiene un estado de tamaño constante; dos pasadas exige releer o almacenar datos. Esto describe los algoritmos, no la memoria total del script, que conserva entradas y resultados para reproducibilidad. Fraction ofrece una referencia exacta aquí, pero operar con enteros crecientes no tiene el coste constante de los float. No ejecutamos un benchmark temporal o de memoria.

Centrar tampoco resuelve cualquier escala: entradas no finitas, desbordamiento de productos, subdesbordamiento de desviaciones diminutas o medias fuera de rango requieren tratamiento adicional. La información perdida antes del cálculo puede exigir otra representación o adquirir diferencias respecto a una referencia menor. Al normalizar entradas de modelos, una varianza pequeña suele entrar en un denominador; la política cerca de cero debe ser explícita. Estos tres números no prueban mejoras predictivas.

La fórmula es una identidad; el algoritmo es una elección

La respuesta es no: equivalencia algebraica no implica equivalencia numérica. En el ejemplo, la varianza exacta 14/9 se conserva en el cálculo centrado y casi íntegramente en el incremental, pero queda sepultada al restar términos grandes en la fórmula directa. El signo negativo es un síntoma útil, no un valor para corregir a ciegas. Antes de culpar al modelo de IA hay que identificar la cantidad pretendida, las entradas representadas y la secuencia aritmética ejecutada.

Fuentes, código y alcance

Leímos las notas técnicas de numpy.var sobre fórmula, ddof y precisión, y las definiciones Python de fsum y ulp. Las derivaciones y la terna sintética son análisis didáctico propio, no reproducción de un benchmark publicado. Documentación NumPy 2.5 y Python 3.14 consultada el 21 de octubre de 2026; entorno ejecutado Python 3.14.0 y NumPy 2.5.3, registrado en los resultados. No hubo ejecución en sensores, GPU o plataformas embedded ni se afirma revisión humana.

NumPy 2.5 — numpy.var.

Python 3.14 — math.fsum, math.ulp.

from fractions import Fraction
xs=[1000000001.0,1000000002.0,1000000004.0]
total=squares=0.0
for x in xs:
    total+=x
    squares+=x*x
raw=squares/len(xs)-(total/len(xs))**2
ys=[Fraction.from_float(x) for x in xs]
mean=sum(ys)/len(ys)
reference=sum((x-mean)**2 for x in ys)/len(ys)
assert raw==-128.0 and reference==Fraction(14,9)
print(raw,reference)

Código, datos e instrucciones · JSON. Cálculos didácticos ejecutados con Python 3.14.0; figuras con Matplotlib 3.11.2. Análisis asistido por IA, sin afirmar revisión por pares ni humana. Portada original ImageGen ilustrativa: no documenta personas, sedes ni instalaciones de EL-AI. Fuentes consultadas el 21 de octubre de 2026.