Algoritmo di sommatoria di Kahan

Da Wikipedia, l'enciclopedia libera.
Vai alla navigazione Vai alla ricerca

In analisi numerica, l'algoritmo di sommatoria di Kahan (conosciuto anche come sommatoria compensata[1]) riduce significativamente l'errore numerico nel totale ottenuto sommando una sequenza di numeri in virgola mobile con precisione finita, se confrontato con il consueto procedimento. Ciò è ottenuto mantenendo una compensazione progressiva separata (una variabile per accumulare piccoli errori).

Quando rappresentiamo un generico numero reale in virgola mobile con precisione finita, ossia con un numero finito di cifre significative, tale rappresentazione, rispetto al numero reale considerato, differisce di un certo valore, il quale corrisponde all'errore di arrotondamento ed è lo scarto tra la rappresentazione in virgola mobile e il numero stesso. Eseguendo una semplice sommatoria di più numeri reali, utilizzando però le rispettive rappresentazioni in virgola mobile, il totale ottenuto presenta un certo errore dato dalla somma algebrica dei singoli errori di arrotondamento, ossia dei singoli scarti, ed, inoltre, si ottiene un certo scarto quadratico medio, in inglese root mean square error, inteso come la radice quadrata della media aritmetica dei quadrati dei singoli scarti.

In particolare, la sommatoria semplice di numeri in sequenza presenta un errore che, nel caso peggiore, cresce proporzionalmente ad ed uno scarto quadratico medio che cresce come per addendi casuali (gli errori di arrotondamento producono una passeggiata aleatoria).[2] Invece, con la sommatoria compensata, l'errore peggiore possibile è indipendente da , dunque un gran numero di valori possono essere sommati con un errore che dipende solo dalla precisione della rappresentazione in virgola mobile.[2]

L'algoritmo è attribuito a William Kahan.[3] Tecniche precedenti simili sono, per esempio, l'algoritmo della linea di Bresenham, che mantiene traccia dell'errore accumulato nelle operazioni sugli interi (anche se già presente in un articolo pubblicato circa nello stesso periodo[4]) e la modulazione Sigma-Delta[5] (che integra e non soltanto somma l'errore).

L'algoritmo[modifica | modifica wikitesto]

In pseudocodice, l'algoritmo è:

function KahanSum(input)
    var sum = 0.0
    var c = 0.0                 // Una compensazione in esecuzione per i bit meno significativi persi.
    for i = 1 to input.length do
        var y = input[i] - c    // Fin qui, tutto bene: c è zero.
        var t = sum + y         // Purtroppo, sum è grande, y è piccola, dunque le cifre meno significative di y vengono perse.
        c = (t - sum) - y       // (t - sum) annulla la parte più significativa di y; sottraendo y si recupera la parte meno significativa di y cambiata di segno
        sum = t                 // Algebricamente, c dovrebbe sempre essere zero. Occorre prestare attenzione ai compilatori con ottimizzazione troppo aggressiva!
    next i                      // La volta successiva, la parte meno significativa persa verrà aggiunta a "y" in un nuovo tentativo. .
    return sum

Esempio pratico[modifica | modifica wikitesto]

Questo esempio sarà dato in decimale. I computer, tipicamente, utilizzano l'aritmetica binaria, ma il principio che ora viene illustrato è lo stesso. Supponiamo di stare utilizzando l'aritmetica in virgola mobile decimale a sei cifre e che sum, la somma, abbia raggiunto il valore 10000,0 e che i prossimi due valori degli input(i) siano 3,14159 e 2,71828. Il risultato esatto è 10005,85987, che si arrotonda a 10005,9. Con una sommatoria semplice ogni valore introdotto sarebbe allineato a sum, la somma, e molte cifre meno significative andrebbero perse (per troncamento o arrotondamento). Il primo risultato, dopo l'arrotondamento, sarebbe 10003,1. Il secondo risultato sarebbe 10005,81828 prima dell'arrotondamento e 10005,8 dopo. Ciò non è corretto.

Tuttavia, con la sommatoria compensata, otteniamo il risultato arrotondato corretto di 10005,9.

Assumiamo che c abbia il valore iniziale zero.

  y = 3.14159 - 0                   y = input[i] - c
  t = 10000.0 + 3.14159
    = 10003.14159                   Ma solo sei cifre vengono mantenute. 
    = 10003.1                       Molte cifre sono andate perse!
  c = (10003.1 - 10000.0) - 3.14159 Questo deve essere valutato come scritto! 
    = 3.10000 - 3.14159             La parte assimilata di y, recuperata, in contrapposizione al valore originale completo di y.
    = -.0415900                     Gli zeri finali vengono visualizzati perché si tratta di aritmetica a sei cifre.
sum = 10003.1                       Dunque, poche cifre da input(i) sono rimaste in quelle di sum.

La somma è così grande che solo le cifre più significative dei numeri immessi vengono accumulate. Ma, al passo successivo, c dà un errore.

  y = 2.71828 - -.0415900           Il deficit ottenuto al passo precedente viene incluso.
    = 2.75987                       È di dimensioni simili a  y : le cifre più significative sono rimaste.
  t = 10003.1 + 2.75987             Ma poche sono rimaste nelle cifre di sum
    = 10005.85987                   e il risultato è arrotondato
    = 10005.9                       a sei cifre.
  c = (10005.9 - 10003.1) - 2.75987 Ciò estrae qualunque cosa sia stata ottenuta.
    = 2.80000 - 2.75987             In tal caso, c'è un eccesso.
    = .040130                       Ma non importa, l'eccesso verrebbe sottratto alla volta successiva.
sum = 10005.9                       Il risultato esatto è 10005.85987, esso è correttamente arrotondato a 6 cifre.

In tal modo, la sommatoria è accumulata utilizzando due variabili: sum che si riferisce alla somma e c in cui si accumulano le parti non assimilate in sum, per spostare nuovamente la parte meno significativa di sum la volta successiva. Quindi la sommatoria procede con delle "cifre di guardia" in c che è meglio di niente ma che non è una cosa buona quanto sarebbe effettuare i calcoli con precisione doppia rispetto all'input. Tuttavia, aumentare semplicemente la precisione dei calcoli, in generale, non è pratico; se l'input è già in precisione doppia, pochi sistemi forniscono una precisione quadrupla e se lo fanno, l'input potrebbe quindi essere in precisione quadrupla.

Accuratezza[modifica | modifica wikitesto]

È necessaria un'attenta analisi degli errori nella sommatoria compensata per apprezzare le sue caratteristiche di accuratezza. Pur essendo più accurata di una sommatoria semplice, essa può ancora dare grandi errori relativi nel caso di somme in particolari cattive condizioni.

Supponiamo di stare sommando valori , per . La somma esatta è:

(calcolata con precisione infinita)

Con la sommatoria compensata, invece, si ottiene , dove l'errore è limitato da:[2]

dove ε è la precisione di macchina dell'aritmetica utilizzata (per esempio, per il formato a precisione doppia in virgola mobile secondo lo standard IEEE). Generalmente, la quantità di interesse è l'errore relativo , che è quindi limitato superiormente da:

Nell'espressione per il limite dell'errore relativo, la frazione è il condizionamento del problema della sommatoria. Essenzialmente, il condizionamento rappresenta la sensibilità intrinseca agli errori del problema della sommatoria, indipendentemente da come è calcolato.[6] Il limite dell'errore relativo di ogni metodo di sommatoria (stabile all'indietro) basato su un algoritmo fissato con precisione fissata (cioè non quelli che usano aritmetica di precisione arbitraria, né algoritmi i cui requisiti di memoria e tempo cambiano in base ai dati), è proporzionale a tale condizionamento.[2] Un problema di sommatoria mal condizionato è uno in cui questo rapporto è grande e in tal caso anche la sommatoria compensata può avere un grande errore relativo. Per esempio, se gli addendi sono numeri casuali non correlati con media zero, la somma è una passeggiata aleatoria e il condizionamento crescerà proporzionalmente a . D'altra parte, per input casuali con media non nulla il condizionamento tende per a una costante finita. Se gli input sono tutti non negativi, allora il condizionamento è .

Dato un certo valore per il condizionamento, l'errore relativo della sommatoria compensata è effettivamente indipendente da . In linea di principio, c'è che cresce linearmente con , ma in pratica questo termine è effettivamente zero: poiché il risultato finale è arrotondato ad una precisione , il termine tende a zero, tranne il caso in cui sia approssimativamente o superiore.[2] In precisione doppia, ciò corrisponde a un di approssimativamente , molto più grande della maggior parte delle somme. Quindi, per un valore fissato del condizionamento, gli errori della sommatoria compensata sono effettivamente , indipendentemente da .

In confronto, il limite dell'errore relativo per la sommatoria semplice (addizionando semplicemente i numeri in sequenza, arrotondamento ad ogni passaggio) cresce come moltiplicato per il condizionamento.[2] Questo errore peggiore possibile, tuttavia, raramente è osservato nella pratica, poiché si verifica solo se gli errori di arrotondamento sono tutti nella stessa direzione. In pratica, è molto più probabile che gli errori di arrotondamento abbiano un segno casuale, con media zero, in modo tale da produrre una passeggiata aleatoria; in questo caso, la sommatoria semplice ha uno scarto quadratico medio, riferito agli errori relativi, che cresce come moltiplicato per il condizionamento.[7] Comunque, questo è ancora molto peggio della sommatoria compensata. Notare, tuttavia, che se la somma può essere eseguita con una precisione doppia, allora viene sostituito da e la sommatoria semplice ha un errore peggiore possibile comparabile con il termine nella sommatoria compensata alla precisione originale.

Per lo stesso motivo, il termine che appare sopra, in , è il limite peggiore possibile che si verifica solo se tutti gli errori di arrotondamento hanno lo stesso segno (e sono della grandezza massima possibile).[2] In pratica, è più probabile che gli errori abbiano un segno casuale, nel qual caso i termini in sono sostituiti da una passeggiata aleatoria; in questo caso, anche per input casuali a media zero, l'errore cresce solo come (ignorando il termine ), con la stessa velocità cresce la somma , cancellando i fattori quando viene calcolato l'errore relativo. Pertanto, anche per somme asintoticamente mal condizionate, l'errore relativo per la sommatoria compensata spesso può essere molto più piccolo di quanto un'analisi del caso peggiore possa suggerire.

Ulteriori miglioramenti[modifica | modifica wikitesto]

Neumaier[8] ha introdotto una leggera modifica dell'algoritmo di Kahan che copre anche il caso in cui il termine successivo da aggiungere è maggiore in valore assoluto rispetto alla somma corrente, scambiando efficacemente il ruolo di ciò che è grande e ciò che è piccolo. In pseudocodice, l'algoritmo è:

 function NeumaierSum(input)
    var sum = input[1]
    var c = 0.0                 // Una compensazione in corso per i bit meno significativi persi.
    for i = 2 to input.length do
        var t = sum + input[i]
        if |sum| >= |input[i]| do
            c += (sum - t) + input[i] // Se sum è più grande, le cifre meno significative di input[i] vanno perse.
        else
            c += (input[i] - t) + sum // Altrimenti, le cifre meno significative di sum vanno perse.
        sum = t
    return sum + c              // Correzione applicata solo una volta alla fine.

Per molte sequenze di numeri, entrambi gli algoritmi concordano ma un simplice esempio dovuto a Peters[9] mostra che essi possono differire. Sommando in doppia precisione, l'algoritmo di Kahan produce mentre l'algoritmo di Neumaier fornisce il valore corretto .

Alternative[modifica | modifica wikitesto]

Sebbene con l'algoritmo di Kahan, sommando numeri, si realizzi una crescita dell'errore dell'ordine di , solo una crescita leggermente peggiore dell'ordine può essere realizzata da una sommatoria ricorsiva a coppie: si divide in modo ricorsivo l'insieme di numeri in due metà, sommando ogni metà, e quindi si addizionano le due somme.[2] Questo presenta il vantaggio di richiedere lo stesso numero di operazioni aritmetiche della sommatoria semplice (a differenza dell'algoritmo di Kahan, che, rispetto a una sommatoria semplice, richiede un numero di operazioni aritmetiche e ha una latenza quattro volte maggiore) e il calcolo può essere eseguito in parallelo. Il modello di base della ricorsione potrebbe essere, in linea di principio, la somma solo di un numero (o zero numeri), ma per contenere il sovraccarico della ricorsione, normalmente, si usa un modello di base più ampio. L'equivalente della sommatoria ricorsiva a coppie è usato in molti algoritmi di trasformata di Fourier veloce (FFT), ed è responsabile della crescita logaritmica degli errori di arrotondamento in tali FFT.[10] In pratica, con errori di arrotondamento di segno casuale, gli scarti quadratici medi della sommatoria ricorsiva a coppie, effettivamente, crescono come .[7]

Un'altra alternativa è usare l'aritmetica a precisione arbitraria, che in linea di principio non richiede affatto arrotondamenti con il costo di uno sforzo computazionale molto maggiore. Un modo per eseguire somme perfettamente arrotondate usando precisione arbitraria consiste nell'estenderle in modo adattivo usando componenti multipli in virgola mobile. Questo minimizzerà il costo computationale nei casi comuni in cui non è necessaria un'elevata precisione.[9][11] Un altro metodo che utilizza solo aritmetica intera, ma un grande accumulatore è stato descritto da Kirchner e Kulisch;[12] un'implementazione hardware è stata descritta da Müller, Rüb e Rülling.[13]

Possibile invalidazione dall'ottimizzazione del compilatore[modifica | modifica wikitesto]

In linea di principio, un'ottimizzazione del compilatore sufficientemente aggressiva potrebbe compromettere l'efficacia della sommatoria di Kahan: per esempio, se il compilatore ha semplificato delle espressioni in base alle regole di associatività dell'aritmetica reale, potrebbe semplificare il secondo passo della sequenza da t = sum + y; c = (t - sum) - y; a ((sum + y) - sum) - y; poi a c = 0;, eliminando la compensazione dell'errore.[14] In pratica, molti compilatori non usano le regole di associatività (che, nell'aritmetica in virgola mobile, sono solo approssimate) nelle semplificazioni, salvo che esplicitamente comandati a farlo dalle opzioni del compilatore che consentano ottimizzazioni non sicure,[15][16][17][18] sebbene il compilatore Intel C++ sia un esempio che consente trasformazioni basate sull'associatività per impostazione predefinita.[19] La versione originale K&R C del linguaggio C consentiva al compilatore di riordinare le espressioni in virgola mobile in base alle regole di associatività dell'aritmetica reale, ma il successivo standard ANSI C non consentiva il riordino allo scopo di rendere il linguaggio C più adatto per le applicazioni numeriche (e più simile al Fortran, che non permette il riordino),[20] anche se, in pratica, le opzioni del compilatore possono abilitare nuovamente il riordino come menzionato sopra.

Supporto con le librerie[modifica | modifica wikitesto]

In generale, le funzioni di somma incluse nei linguaggi di programmazione tipicamente non forniscono alcuna garanzia che venga utilizzato un particolare algoritmo di sommatoria, come la sommatoria di Kahan.[senza fonte] Lo standard BLAS (Basic Linear Algebra Subprograms) per le subroutines di algebra lineare evita esplicitamente di imporre un particolare ordine di calcolo delle operazioni per motivi di prestazioni,[21] e le implementazioni BLAS, tipicamente, non utilizzano la sommatoria di Kahan.

La libreria standard del linguaggio Python specifica una funzione fsum. per sommatorie arrotondate esattamente, usando l'algoritmo di Shewchuk[9] per tracciare più somme parziali.

Nel linguaggio Julia, l'implementazione predefinita della funzione sum utilizza la sommatoria ricorsiva a coppie per un'elevata accuratezza con buone prestazioni,[22] ma la libreria standard ha anche un'implementazione della variante di Neumaier's chiamata sum_kbn per i casi in cui è richiesta un'accuratezza più elevata.[23]

Note[modifica | modifica wikitesto]

  1. ^ Rigorosamente, esistono anche altre varianti di sommatoria compensata: vedere (EN) Nicholas Higham, Accuracy and Stability of Numerical Algorithms, 2ª ed., SIAM, 2002, pp. 110–123.
  2. ^ a b c d e f g h Nicholas J. Higham, The accuracy of floating point summation, in SIAM Journal on Scientific Computing, vol. 14, n. 4, 1993, pp. 783–799, DOI:10.1137/0914050.
  3. ^ (EN) William Kahan, Further remarks on reducing truncation errors, in Communications of the ACM, vol. 8, n. 1, gennaio 1965, p. 40, DOI:10.1145/363707.363723.
  4. ^ (EN) Jack E. Bresenham, "Algorithm for computer control of a digital plotter" (PDF), in IBM Systems Journal, vol. 4, n. 1, gennaio 1965, pp. 25–30. URL consultato il 29 aprile 2019 (archiviato dall'url originale il 28 maggio 2008).
  5. ^ (EN) H. Inose, Y. Yasuda, J. Murakami, "A Telemetering System by Code Manipulation – ΔΣ Modulation," IRE Trans on Space Electronics and Telemetry, Sep. 1962, pp. 204–209.
  6. ^ (EN) L. N. Trefethen e D. Bau, Numerical Linear Algebra (SIAM: Philadelphia, 1997).
  7. ^ a b (EN) Manfred Tasche e Hansmartin Zeuner Handbook of Analytic-Computational Methods in Applied Mathematics Boca Raton, FL: CRC Press, 2000).
  8. ^ (DE) A. Neumaier, Rundungsfehleranalyse einiger Verfahren zur Summation endlicher Summen, in Zeitschrift für Angewandte Mathematik und Mechanik, vol. 54, n. 1, 1974, pp. 39–51.
  9. ^ a b c (EN) Raymond Hettinger, Recipe 393090: Binary floating point summation accurate to full precision, Python implementation of algorithm from Shewchuk (1997) paper (28 March 2005).
  10. ^ (EN) S. G. Johnson e M. Frigo, Implementing FFTs in practice, in C. Sidney Burrus (a cura di), Fast Fourier Transforms, 2008.
  11. ^ (EN) Jonathan R. Shewchuk, Adaptive Precision Floating-Point Arithmetic and Fast Robust Geometric Predicates (PDF)., Discrete and Computational Geometry, vol. 18, pp. 305–363 (October 1997).
  12. ^ (EN) R. Kirchner, U. W. Kulisch, Accurate arithmetic for vector processors, Journal of Parallel and Distributed Computing 5 (1988) 250-270
  13. ^ (EN) M. Muller, C. Rub, W. Rulling [1], Exact accumulation of floating-point numbers, Proceedings 10th IEEE Symposium on Computer Arithmetic (Jun 1991), doi 10.1109/ARITH.1991.145535
  14. ^ (EN) David Goldberg, What every computer scientist should know about floating-point arithmetic (PDF), in ACM Computing Surveys, vol. 23, n. 1, marzo 1991, pp. 5–48, DOI:10.1145/103162.103163.
  15. ^ (EN) GNU Compiler Collection manual, version 4.4.3: 3.10 Options That Control Optimization., -fassociative-math (Jan. 21, 2010).
  16. ^ Compaq Fortran User Manual for Tru64 UNIX and Linux Alpha Systems (archiviato dall'url originale il 7 giugno 2011)., section 5.9.7 Arithmetic Reordering Optimizations (accesso March 2010).
  17. ^ (EN) Börje Lindh, Application Performance Optimization (PDF) (archiviato dall'url originale il 2 giugno 2010)., Sun BluePrints OnLine (March 2002).
  18. ^ (EN) Eric Fleegal, " Microsoft Visual C++ Floating-Point Optimization.", Microsoft Visual Studio Technical Articles (June 2004).
  19. ^ (EN) Martyn J. Corden, " Consistency of floating-point results using the Intel compiler," Intel technical report (Sep. 18, 2009).
  20. ^ (EN) Tom Macdonald, "C for Numerical Computing", Journal of Supercomputing vol. 5, pp. 31–48 (1991).
  21. ^ (EN) BLAS Technical Forum, su netlib.org, 21 agosto 2001, p. section 2.7 (archiviato dall'url originale il 10 aprile 2004).
  22. ^ (EN) https://docs.julialang.org/en/stable/stdlib/collections/?highlight=sum#Base.sum
  23. ^ (EN) https://docs.julialang.org/en/stable/stdlib/arrays/?highlight=sum_kbn#Base.sum_kbn

Bibliografia[modifica | modifica wikitesto]

  • Luciano M. Barone, Enzo Marinari, Giovanni Organtini e Federico Ricci-Terseng, Programmazione Scientifica. Linguaggio C, algoritmi e modelli nella scienza, Pearson Education Italia, 2006, p. 108, paragrafo 4.5: Un problema di arrotondamento, ISBN 978-88-7192-242-3.

Voci correlate[modifica | modifica wikitesto]

Collegamenti esterni[modifica | modifica wikitesto]