ELAI S.r.l.

Una varianza negativa: perché una formula corretta può sbagliare nel computer

Tre numeri, una varianza esatta di 14/9 e un risultato di −128: derivazione, precisione finita e confronto riproducibile fra algoritmi.

Una varianza negativa: perché una formula corretta può sbagliare nel computer

Quando un numero impossibile segnala un problema reale

Una pipeline AI calcola quanto varia una grandezza prima di normalizzare i dati o controllare un sensore. I valori sono grandi, ma le differenze fra loro sono piccole. Il programma restituisce una varianza negativa. Poiché la varianza è una media di quadrati, quel segno non può descrivere il fenomeno matematico atteso. Dobbiamo cercare un errore nei dati, nella formula oppure nel modo in cui le operazioni vengono eseguite? In questo articolo isoliamo la terza possibilità con tre numeri sintetici.

La domanda è precisa: due espressioni equivalenti sui numeri reali restano intercambiabili in precisione finita? Vedremo perché la risposta è no, confronteremo un calcolo centrato in due passaggi con un aggiornamento incrementale e distingueremo errore di calcolo da informazione già persa nell’ingresso. Non misuriamo una linea produttiva né l’accuratezza di un modello EL-AI. Le unità sono generiche: se i dati fossero in volt, la varianza sarebbe in volt quadrati; nessun dato proviene da un voltmetro reale.

Misurare gli scarti prima di scegliere l’algoritmo

La media aritmetica è il punto di equilibrio dei valori. Uno scarto è la differenza fra un valore e quella media. La varianza descrive la dimensione media degli scarti al quadrato: elevare al quadrato evita che differenze positive e negative si cancellino. Indichiamo con n il numero di osservazioni, con x_i il valore i-esimo e con mu la media. Qui dividiamo per n, cioè usiamo la varianza della popolazione finita composta dai nostri tre valori.

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

L’ultima uguaglianza si ottiene sviluppando ogni quadrato. La somma del termine −2 mu x_i vale −2 n mu²; il termine mu² compare n volte. Dopo la divisione per n rimane la media dei quadrati meno il quadrato della media. In aritmetica esatta il risultato è lo stesso e non può essere negativo. L’ultima espressione appare conveniente perché accumula soltanto somma e somma dei quadrati. Ma la comodità algebrica non garantisce stabilità numerica.

La correzione n−1 risponde a un’altra domanda statistica: stimare la varianza di una popolazione più ampia da un campione, sotto le opportune ipotesi. Non ripara la cancellazione numerica. Con questi tre valori, cambiare denominatore moltiplicherebbe la varianza per 3/2; non recupererebbe cifre perdute. Nelle chiamate NumPy usiamo esplicitamente ddof=0, così il confronto riguarda lo stesso oggetto matematico.

Il caso guida, svolto anche senza computer

Prendiamo b=1.000.000.000 e i tre valori b+1, b+2, b+4. La media è b+7/3. Gli scarti sono −4/3, −1/3 e 5/3: i loro quadrati sommano 42/9 e la divisione per tre dà 14/9. Il grande valore b scompare perché una traslazione comune non cambia le distanze dalla media. La varianza esatta è circa 1,5555555556, non zero e certamente non negativa.

Il programma eseguito con Python 3.14.0, float con 53 bit di significando e accumulo sequenziale restituisce invece −128 con la formula grezza. La ricerca è deterministica: basi 10^k per k da 0 a 16 e incrementi fissi 1, 2, 4. Il primo risultato negativo, in quest’ordine, compare a k=9. Pubblicare il criterio di ricerca evita di spacciare il caso scelto per una frequenza di errore. Non stiamo affermando che ogni terna o ogni calcolatore produca −128.

Dove spariscono le cifre

Un numero floating point conserva una quantità finita di cifre significative, non un numero fisso di cifre dopo la virgola. Crescendo la grandezza cresce anche la distanza fra numeri rappresentabili. Intorno a b la distanza, misurata da math.ulp(b), è circa 1,19×10⁻⁷: i nostri incrementi interi sono ben distinguibili. Il problema di k=9 non nasce quindi dal fatto che 1, 2 e 4 siano già scomparsi durante l’ingresso. Nasce nelle operazioni successive, quando eleviamo valori grandi al quadrato.

Nella formula grezza sottraiamo due quantità vicine a 10¹⁸ per ottenere un residuo vicino a 1,56. Gli arrotondamenti nei prodotti, nelle somme e nella media possono essere grandi rispetto a quel residuo. La sottrazione espone questa incertezza: cancella le cifre comuni dei due grandi termini senza ricreare quelle che non erano più disponibili. Non è corretto dire che ogni sottrazione di numeri vicini sia di per sé inaccurata; è l’errore già presente negli operandi a dominare il risultato che vogliamo estrarre.

Sostituire la somma con math.fsum è una verifica utile ma non una cura universale. fsum migliora l’accumulo dei valori che riceve; non rende esatti i prodotti x*x già arrotondati né il quadrato della media. Nel caso guida anche la variante con fsum restituisce −128. Il rimedio max(v,0) eliminerebbe il segno impossibile producendo zero, ma la varianza corretta rimane 14/9. Può essere una politica di contenimento in un sistema più ampio, mai una prova di aver recuperato l’informazione.

Centrare prima di elevare al quadrato

Il metodo in due passaggi calcola prima una stima della media e poi somma i quadrati degli scarti da essa. Le quantità elevate al quadrato sono piccole: non formiamo due termini enormi da sottrarre alla fine. Nel nostro codice usiamo fsum anche per media e scarti. Se la media calcolata è mu+e, in aritmetica esatta la media dei quadrati intorno a quel centro è v+e², perché la somma degli scarti veri è zero. Questo chiarisce perché un piccolo errore nel centro non si traduca automaticamente in un grande errore sulla varianza. Le altre operazioni continuano comunque ad arrotondare.

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

Per un flusso che non vogliamo rileggere, l’aggiornamento comunemente chiamato Welford mantiene conteggio, media corrente e M2, la somma degli scarti quadratici centrati. Quando arriva x_k, delta è la sua distanza dalla vecchia media; la nuova media si sposta di delta/k. Aggiorniamo M2 con delta moltiplicato per la distanza dalla nuova media. La varianza finale è M2/n, non M2/(n−1) nel confronto che abbiamo scelto.

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

La ricorrenza si può verificare senza invocare una libreria. Spostando il centro dei k−1 vecchi valori di delta/k, il loro contributo aumenta di (k−1)delta²/k², poiché gli scarti dalla vecchia media sommano zero. Il nuovo valore ha distanza delta(k−1)/k dalla nuova media, quindi aggiunge delta²(k−1)²/k². La somma dei due incrementi è delta²(k−1)/k, uguale a delta(x_k−mu_k). La derivazione è esatta sui reali; il programma non eredita per questo precisione infinita.

Risultati e significato del riferimento esatto

MetodoVarianza, unità²
Formula grezza-128
Formula grezza + fsum-128
Due passaggi1.5555555555555571
Welford1.555555522441864
NumPy 2.5.3 float641.5555555555555571
Frazione esatta14/9

Il metodo in due passaggi e NumPy concordano in questo caso; Welford rimane molto vicino al valore corretto ma non è identico. La tabella non è una gara universale fra librerie. NumPy usa l’array float64 e ddof=0; gli altri algoritmi sono implementazioni brevi del nostro esperimento. Il riferimento Fraction.from_float trasforma ciascun ingresso floating point in una frazione esatta e calcola poi la varianza senza arrotondamento. Con k=9 gli ingressi interi sono rappresentati esattamente, quindi quel riferimento coincide anche con i valori che intendevamo generare.

Alle basi maggiori cambia un punto fondamentale. Intorno a 10¹⁶ la distanza fra float adiacenti è 2: l’incremento 1 non è più sempre conservato come desiderato. Un algoritmo di varianza non può ricostruirlo dopo la conversione. Per questo il grafico misura l’errore rispetto alla varianza esatta degli ingressi effettivamente rappresentati, non assume che la varianza resti 14/9 per tutta la scansione. Scegliere l’oracolo sbagliato mescolerebbe due problemi diversi: rappresentazione degli ingressi e stabilità del calcolo.

A sinistra errore assoluto rispetto agli ingressi rappresentati, per basi 10^k e incrementi 1, 2, 4; scala simmetrica logaritmica, regione lineare entro ±10⁻¹⁴ per mostrare anche lo zero. A destra il caso k=9. Le linee sovrapposte non indicano precisione universale.
A sinistra errore assoluto rispetto agli ingressi rappresentati, per basi 10^k e incrementi 1, 2, 4; scala simmetrica logaritmica, regione lineare entro ±10⁻¹⁴ per mostrare anche lo zero. A destra il caso k=9. Le linee sovrapposte non indicano precisione universale.

Il programma prova inoltre tutte le sei permutazioni della terna guida. Nel nostro ambiente la formula grezza rimane a −128, mentre Welford varia circa fra 1,5555555224 e 1,5555555820. È un promemoria concreto: in precisione finita anche algoritmi più stabili possono dipendere dall’ordine. La scansione di basi e le permutazioni sono controlli deterministici, non una dimostrazione formale di stabilità per ogni sequenza. I risultati completi sono nel JSON, senza selezionare soltanto i casi favorevoli.

Scegliere il metodo senza confondere i compromessi

Sia due passaggi sia Welford richiedono un numero di operazioni proporzionale a n. Welford conserva uno stato di dimensione costante; due passaggi richiede di poter rileggere i dati o averli memorizzati. Questa descrizione riguarda gli algoritmi, non la memoria totale del nostro script, che salva ingressi e risultati per la riproducibilità. Fraction offre un riferimento esatto per questo piccolo caso, ma l’aritmetica su interi di dimensione crescente non ha il costo costante dei float. Non abbiamo eseguito un benchmark di tempo o memoria.

Neppure centrare i dati risolve ogni scala possibile: valori non finiti, overflow dei prodotti, underflow di scarti minuscoli o media fuori dal campo rappresentabile richiedono ulteriore trattamento. Se l’informazione è persa prima del calcolo, può servire cambiare rappresentazione o acquisire differenze rispetto a un riferimento più piccolo. Se usiamo statistiche per normalizzare un modello, una piccola varianza entra spesso in un denominatore: anche la politica per valori quasi nulli deve essere esplicita. Non possiamo dedurre da questi tre numeri un miglioramento di qualità predittiva.

La formula è un’identità; l’algoritmo è una scelta

La risposta iniziale è dunque no: equivalenza algebrica non implica equivalenza numerica. Nel caso guida la varianza esatta 14/9 sopravvive nel calcolo centrato e quasi integralmente in quello incrementale, ma viene travolta dalla sottrazione dei grandi termini della formula grezza. Il segno negativo è un sintomo utile, non un valore da correggere alla cieca. Prima di attribuire un’anomalia al modello AI, bisogna sapere quale quantità si voleva calcolare, con quali ingressi rappresentati e con quale sequenza di operazioni.

Fonti, codice e limiti dell’analisi

Sono state lette le note tecniche di numpy.var su formula, ddof e precisione, e le definizioni Python di fsum e ulp. Le derivazioni e la terna sintetica sono l’analisi didattica di questo articolo; non riproducono un benchmark pubblicato. Documentazione NumPy 2.5 e documentazione Python 3.14 consultate il 21 ottobre 2026; ambiente eseguito Python 3.14.0 e NumPy 2.5.3, come registrato nei risultati. Nessuna esecuzione su sensori, GPU o piattaforme embedded, nessuna revisione umana attestata.

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)

Codice, dati e istruzioni · JSON. Calcoli didattici eseguiti con Python 3.14.0; figure con Matplotlib 3.11.2. Analisi con assistenza AI, senza dichiarare peer review o revisione umana. Copertina originale ImageGen, illustrativa: non documenta persone, sedi o installazioni EL-AI. Fonti consultate il 21 ottobre 2026.