Quale algoritmo è più preciso per calcolare la somma di una matrice ordinata di numeri?


22

Dato è una sequenza finita crescente di numeri positivi . Quale dei seguenti due algoritmi è migliore per calcolare la somma dei numeri?z1,z2,.....zn

s=0; 
for \ i=1:n 
    s=s + z_{i} ; 
end

O:

s=0; 
for \ i=1:n 
s=s + z_{n-i+1} ; 
end

Secondo me sarebbe meglio iniziare ad aggiungere i numeri dal numero più grande al numero più piccolo, perché l'errore diventa sempre più piccolo. Sappiamo anche che quando aggiungiamo un numero molto grande a un numero molto piccolo, il risultato approssimativo può essere il numero grande.

È corretto? che altro si può dire?

Risposte:


18

L'aggiunta di numeri arbitrari in virgola mobile genererà in genere un errore di arrotondamento e l'errore di arrotondamento sarà proporzionale alla dimensione del risultato. Se si calcola una singola somma e si inizia aggiungendo prima i numeri più grandi, il risultato medio sarà maggiore. Quindi inizieresti ad aggiungere con i numeri più piccoli.

Ma ottieni risultati migliori (e funziona più velocemente) se produci quattro somme, ad esempio: Inizia con sum1, sum2, sum3, sum4 e aggiungi quattro elementi di array a turno per sum1, sum2, sum3, sum4. Poiché ogni risultato è in media solo 1/4 della somma originale, l'errore è quattro volte più piccolo.

Meglio ancora: aggiungi i numeri in coppia. Quindi aggiungere i risultati in coppia. Aggiungi nuovamente i risultati in coppia e così via fino a quando non ti rimangono due numeri da aggiungere.

Molto semplice: usa una precisione maggiore. Usa il doppio lungo per calcolare una somma di doppi. Usa double per calcolare una somma di float.

Quasi perfetto: cerca l'algoritmo di Kahan, descritto in precedenza. Meglio ancora usato aggiungendo a partire dal numero più piccolo.


26

Sono numeri interi o numeri in virgola mobile? Supponendo che sia in virgola mobile, sceglierei la prima opzione. È meglio aggiungere i numeri più piccoli tra loro, quindi aggiungere i numeri più grandi in seguito. Con la seconda opzione, si finirà per l'aggiunta di un piccolo numero ad un grande numero come i aumenta, che può portare a problemi. Ecco una buona risorsa sull'aritmetica in virgola mobile: ciò che ogni scienziato informatico dovrebbe sapere sull'aritmetica in virgola mobile


24

La risposta di animal_magic è corretta: dovresti aggiungere i numeri dal più piccolo al più grande, tuttavia voglio fare un esempio per mostrare il perché.

Supponiamo che stiamo lavorando in un formato a virgola mobile che ci dà una sconcertante 3 cifre di precisione. Ora vogliamo aggiungere dieci numeri:

[1000, 1, 1, 1, 1, 1, 1, 1, 1, 1]

Ovviamente la risposta esatta è 1009, ma non possiamo ottenerla nel nostro formato a 3 cifre. Arrotondando a 3 cifre, la risposta più accurata che otteniamo è 1010. Se aggiungiamo il più piccolo al più grande, su ogni ciclo otteniamo:

Loop Index        s
1                 1
2                 2
3                 3
4                 4
5                 5
6                 6
7                 7
8                 8
9                 9
10                1009 -> 1010

Quindi otteniamo la risposta più accurata possibile per il nostro formato. Ora supponiamo che aggiungiamo dal più grande al più piccolo.

Loop Index        s
1                 1000
2                 1001 -> 1000
3                 1001 -> 1000
4                 1001 -> 1000
5                 1001 -> 1000
6                 1001 -> 1000
7                 1001 -> 1000
8                 1001 -> 1000
9                 1001 -> 1000
10                1001 -> 1000

Poiché i numeri in virgola mobile vengono arrotondati dopo ogni operazione, tutte le aggiunte vengono arrotondate, aumentando il nostro errore da 1 a 9 dall'esatto. Ora immagina se il tuo set di numeri da aggiungere avesse un 1000, quindi un centinaio di 1 o un milione. Nota che per essere veramente accurati, dovresti sommare i due numeri più piccoli, quindi ricorrere al risultato nel tuo set di numeri.


15

Per il caso generale, utilizzerei la somma compensata (o somma Kahan). A meno che i numeri non siano già ordinati, ordinarli sarà molto più costoso che aggiungerli . La somma compensata è anche più accurata della somma ordinata o della somma ingenua (vedere il collegamento precedente).

Per quanto riguarda i riferimenti, ciò che ogni programmatore dovrebbe sapere sull'aritmetica in virgola mobile copre i punti di base in modo sufficientemente dettagliato che qualcuno potrebbe leggerlo in 20 (+/- 10) minuti e comprendere le basi. "Quello che ogni scienziato informatico dovrebbe sapere sull'aritmetica in virgola mobile" di Goldberg è il riferimento classico, ma la maggior parte delle persone che conosco che raccomandano che il giornale non lo leggano in dettaglio, perché sono circa 50 pagine (più di questo, in alcune stampe), e scritto in fitta prosa, quindi ho difficoltà a consigliarlo come riferimento di prima linea per le persone. È utile per una seconda occhiata al soggetto. Un riferimento enciclopedico è l' accuratezza e la stabilità di Higham degli algoritmi numerici, che copre questo materiale, nonché l'accumulo di errori numerici in molti altri algoritmi; sono anche 680 pagine, quindi non guarderei neanche prima questo riferimento.


2
Per completezza, nel libro di Higham troverai la risposta alla domanda originale a pagina 82 : l'ordinamento crescente è il migliore. C'è anche una Sezione (4.6) che discute la scelta del metodo.
Federico Poloni,

7

Le risposte precedenti già trattano la questione in generale e danno buoni consigli, ma c'è una stranezza aggiuntiva che vorrei menzionare. Sulla maggior parte delle architetture moderne, il forloop che hai descritto verrebbe eseguito comunque con una precisione estesa a 80 bit , il che garantisce un'accuratezza aggiuntiva, poiché tutte le variabili temporanee verranno inserite nei registri. Quindi hai già qualche forma di protezione dagli errori numerici. Tuttavia, in loop più complicati, i valori intermedi verranno memorizzati nella memoria tra le operazioni e quindi troncati a 64 bit. immagino che

s=0; 
for \ i=1:n 
    printf("Hello World");
    s=s + z_{i} ; 
end

è sufficiente ottenere una precisione inferiore nella somma (!!). Quindi fai molta attenzione se vuoi stampare il debug del tuo codice mentre controlli l'accuratezza.

Per gli interessati, questo documento descrive un problema in una routine numerica ampiamente utilizzata (fattorizzazione QR rivelatrice di rango di Lapack) il cui debug e analisi sono stati molto complicati proprio a causa di questo problema.


1
Le macchine più moderne sono a 64 bit e usano unità SSE o AVX anche per operazioni scalari. Quelle unità non supportano l'aritmetica a 80 bit e usano la stessa precisione interna degli argomenti dell'operazione. L'uso della FPU x87 è generalmente sconsigliato ora e la maggior parte dei compilatori a 64 bit ha bisogno di opzioni speciali per essere costretti a usarlo.
Hristo Iliev,

1
@HristoIliev Grazie per il commento, non lo sapevo!
Federico Poloni,

4

Delle 2 opzioni, l'aggiunta dal più piccolo al più grande produrrà meno errori numerici, quindi l'aggiunta dal più grande al più piccolo.

Tuttavia,> 20 anni fa nella mia classe "Metodi numerici" l'istruttore ha dichiarato questo e mi è venuto in mente che questo stava ancora introducendo più errori del necessario a causa della relativa differenza di valore tra l'accumulatore e i valori che venivano aggiunti.

Logicamente, una soluzione preferibile è aggiungere i 2 numeri più piccoli nell'elenco, quindi reinserire il valore sommato nell'elenco ordinato.

Per dimostrarlo, ho elaborato un algoritmo che poteva farlo in modo efficiente (nello spazio e nel tempo) usando lo spazio liberato mentre gli elementi venivano rimossi dall'array primario per costruire un array secondario dei valori sommati che erano intrinsecamente ordinati dopo le aggiunte erano delle somme di valori che erano sempre in aumento. Ad ogni iterazione vengono quindi controllati i "suggerimenti" di entrambi gli array per trovare i 2 valori più piccoli.


2

Poiché non hai limitato il tipo di dati da utilizzare, per ottenere un risultato perfettamente accurato, utilizza semplicemente numeri di lunghezza arbitrari ... nel qual caso l'ordine non importerà. Sarà molto più lento, ma ottenere la perfezione richiede tempo.


0

Usa aggiunta dell'albero binario, ovvero Scegli la media della distribuzione (numero più vicino) come radice dell'albero binario e crea un albero binario ordinato aggiungendo valori minori a sinistra del grafico e più grandi a destra e così via . Aggiunge ricorsivamente tutti i nodi figlio di un genitore singolo in un approccio dal basso verso l'alto. Ciò sarà efficiente quando l'errore avg aumenta con il numero di sommazioni e in un approccio ad albero binario, il numero di sommazioni è nell'ordine del log n nella base 2. Quindi l'errore avg sarebbe minore.


Ciò equivale all'aggiunta di coppie adiacenti nell'array originale (poiché è ordinato). Non c'è motivo di inserire tutti i valori nell'albero.
Godric Seer,

0

Ciò che Hristo Iliev ha detto sopra sui compilatori a 64 bit che preferiscono le istruzioni SSE e AVX sull'FPU (AKA NDP) è assolutamente vero, almeno per Microsoft Visual Studio 2013. Tuttavia, per le operazioni a virgola mobile a precisione doppia che stavo usando ho trovato in realtà è più veloce, così come in teoria più preciso, utilizzare la FPU. Se per te è importante, suggerirei di provare prima varie soluzioni, prima di scegliere un approccio finale.

Quando lavoro in Java, utilizzo molto frequentemente il tipo di dati BigDecimal a precisione arbitraria. È semplicemente troppo facile e di solito non si nota la diminuzione della velocità. Il calcolo delle funzioni trascendentali con serie e sqrt infinite utilizzando il metodo di Newton può richiedere un millesimo di secondo o più, ma è fattibile e abbastanza preciso.


0

L'ho lasciato solo qui /programming//a/58006104/860099 (quando ci vai, fai clic su "mostra frammento di codice" ed eseguilo con il pulsante

È un esempio di JavaScript che mostra chiaramente che la somma a partire dal più grande dà un errore maggiore

arr=[9,.6,.1,.1,.1,.1];

sum     =             arr.reduce((a,c)=>a+c,0);  // =  9.999999999999998
sortSum = [...arr].sort().reduce((a,c)=>a+c,0);  // = 10

console.log('sum:     ',sum);
console.log('sortSum:',sortSum);

Le risposte solo al collegamento sono sconsigliate in questo sito. Puoi spiegare cosa viene fornito nel link?
Nicoguaro

@nicoguaro aggiorno la risposta - tutte le risposte sono molto belle, ma ecco un esempio concreto
Kamil Kiełczewski
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.