2010-03-16 6 views
10

Supponiamo di avere 100000000 valori in virgola mobile a 32 bit in un array e ognuno di questi float ha un valore compreso tra 0.0 e 1.0. Se si è tentato di riassumere tutti in su come questoQual è un buon modo per aggiungere un gran numero di piccoli oggetti galleggianti?

result = 0.0; 
for (i = 0; i < 100000000; i++) { 
    result += array[i]; 
} 

che ci si esegue in problemi come result ottiene molto più grande di 1,0.

Quindi quali sono alcuni dei modi per eseguire la sommatoria in maniera più accurata?

+2

perché ti aspetti che il risultato sia inferiore a 1? Non ho capito bene! – lexu

+0

Penso che stia dicendo che * una volta * il risultato supera 1.0 i problemi iniziano a sorgere. * Quali * problemi non conosco, ma è così che l'ho preso. –

+0

In Python, usate 'math.fsum' (http://docs.python.org/library/math.html#math.fsum). – kennytm

risposta

27

Suona come si desidera utilizzare Kahan Summation.

Secondo Wikipedia,

Il Kahan algoritmo somma (noto anche come sommatoria compensato) riduce significativamente l'errore numerico nel totale ottenuta aggiungendo una sequenza di numeri in virgola precisione finita galleggiamento, rispetto all'approccio ovvio. Questo viene fatto mantenendo una compensazione di corsa separata (una variabile per accumulare piccoli errori).

In pseudocodice, l'algoritmo è:

function kahanSum(input) 
var sum = input[1] 
var c = 0.0   //A running compensation for lost low-order bits. 
for i = 2 to input.length 
    y = input[i] - c //So far, so good: c is zero. 
    t = sum + y   //Alas, sum is big, y small, so low-order digits of y are lost. 
    c = (t - sum) - y //(t - sum) recovers the high-order part of y; subtracting y recovers -(low part of y) 
    sum = t    //Algebraically, c should always be zero. Beware eagerly optimising compilers! 
next i    //Next time around, the lost low part will be added to y in a fresh attempt. 
return sum 
+2

+1 per aver tentato di rispondere alla domanda effettiva del poster. –

+0

Proprio quello che stavo cercando! Grazie :) – splicer

+0

Felice di poterti aiutare. –

1

Rendere un risultato doppio, presupponendo C o C++.

+0

Sì, questo sarà d'aiuto, ma cosa succederebbe se si sommano più di 100000000 valori? La mia scelta di 100000000 per questa domanda era arbitraria. – splicer

0

Se in .NET si utilizza il metodo di estensione LINQ .Sum() esistente su un oggetto IEnumerable. Poi sarebbe solo:

var result = array.Sum(); 
+0

Grazie, ma dovrei essere più specifico: sto lavorando in C e OpenCL. – splicer

+0

Anche questo non risolve il problema di accumulo di errori. – recursive

1

Se si può tollerare un po 'di spazio in più (in Java):

float temp = new float[1000000]; 
float temp2 = new float[1000]; 
float sum = 0.0f; 
for (i=0 ; i<1000000000 ; i++) temp[i/1000] += array[i]; 
for (i=0 ; i<1000000 ; i++) temp2[i/1000] += temp[i]; 
for (i=0 ; i<1000 ; i++) sum += temp2[i]; 

standard algoritmo divide et impera, in fondo. Funziona solo se i numeri sono sparsi casualmente; non funzionerà se il primo mezzo miliardo di numeri è 1e-12 e il secondo mezzo miliardo è molto più grande.

Ma prima di fare qualsiasi cosa, si potrebbe semplicemente accumulare il risultato in un doppio. Questo aiuterà molto.

0

Il modo assolutamente ottimale è quello di utilizzare una coda di priorità, nel seguente modo:

PriorityQueue<Float> q = new PriorityQueue<Float>(); 
for(float x : list) q.add(x); 
while(q.size() > 1) q.add(q.pop() + q.pop()); 
return q.pop(); 

(questo codice assume i numeri sono positivi, generalmente coda devono essere ordinati in valore assoluto)

Spiegazione: dato un elenco di numeri, per aggiungerli nel modo più preciso possibile, dovresti cercare di chiudere i numeri, ti eliminare la differenza tra piccoli e grandi. Ecco perché vuoi sommare i due numeri più piccoli, aumentando così il valore minimo della lista, diminuendo la differenza tra il minimo e il massimo nell'elenco e riducendo la dimensione del problema di 1.

Purtroppo non ho idea di come questo può essere vettorializzato, considerando che stai usando OpenCL. Ma sono quasi sicuro che possa essere. Potresti dare un'occhiata al libro sugli algoritmi vettoriali, è sorprendente quanto siano potenti in realtà: Vector Models for Data-Parallel Computing

+2

In realtà questa non è una soluzione ottimale. Vuoi minimizzare il valore assoluto dei risultati intermedi, il che non significa necessariamente che devi sempre aggiungere prima i numeri più piccoli. Ad esempio, se si desidera sommare [1.01, -0.001, -1.02, 0.0012], è meglio esprimerlo come (0.0012 - 0.001) + (1.01 - 1.02). –