2011-01-27 19 views
12

Devo calcolare l'errore quadratico medio di un'operazione a 16 bit per un numero arbitrario di punti dati (verso l'alto di 100 milioni). Ho deciso di utilizzare una media costante, quindi non dovrei preoccuparmi di un overflow dovuto all'aggiunta di un numero elevato di errori al quadrato. A 100 milioni di campioni ho avuto problemi con la precisione in virgola mobile (risultati inaccurati), quindi mi sono trasferito al raddoppio.Mantenimento della precisione di virgola mobile con una media mobile

Ecco il mio codice

int iDifference = getIdeal() - getValue(); 

m_iCycles++; 


// calculate the running MSE as 

// http://en.wikipedia.org/wiki/Moving_average 

// MSE(i + 1) = MSE(i) + (E^2 - MSE(i))/(i + 1) 

m_dMSE = m_dMSE + ((pow((double)iDifference,2) - m_dMSE)/(double)m_iCycles); 

Esiste un modo migliore per implementare questo per mantenere la precisione? Ho considerato di normalizzare il MSE a uno e semplicemente di mantenere una somma con una divisione finale al completamento per calcolare la media.

+1

Come nota a lato del tutto, a seconda se si prende in mano il 'pow (double, int)' sovraccarico, 'iDifference * iDifference' potrebbe essere ordini di grandezza più veloce rispetto alla chiamata' pow'. –

+0

concordato. Avrei dovuto prenderlo. Grazie Marco! –

risposta

4

I numeri in virgola mobile non eccedono in questo tipo di situazione, perdono solo precisione. Quindi non ci sono vantaggi di una media costante su un totale parziale qui. La conseguenza è la stessa se il totale parziale o il denominatore cresce.

Per mantenere la precisione in un totale parziale, mantenere i subtotali anziché un singolo totale. Continuate ad aggiungere a un subtotale fino a quando aggiungerne uno causerebbe un overflow. Quindi passare al subtotale successivo. Poiché sono tutti dello stesso ordine di grandezza (in base 2), la precisione ottimale può essere ottenuta convertendo in virgola mobile e usando un accumulo a coppie in un totale finale.

// first = errors, second = counter 
typedef pair< vector<uint32_t>, uint32_t > running_subtotals; 

void accumulate_error(uint32_t error, running_subtotals &acc) { 
    (numeric_limits<uint32_t>::max() - error < acc.first.back()? 
     * acc.first.insert(acc.first.end(), 0) : acc.first.back()) 
     += error; // add error to current subtotal, or new one if needed 
    ++ acc.second; // increment counter 
} 

double get_average_error(running_subtotals const &total) { 
    vector<double> acc(total.first.begin(), total.first.end()); 
    while (acc.size() != 1) { 
     if (acc.size() % 2) acc.push_back(0); 
     for (size_t index = 0; index < acc.size()/2; ++ index) { 
      acc[ index ] = acc[ index * 2 ] + acc[ index * 2 + 1 ]; 
     } 
     acc.resize(acc.size()/2); 
    } 
    return acc.front()/total.second; 
} 
+0

La soluzione funziona alla grande. –

+0

"I numeri in virgola mobile non eccedono in questo tipo di situazione, perdono solo precisione." La perdita di precisione non può portare all'overflow? Un galleggiante meno preciso può essere più piccolo * o * più grande del valore reale, non è vero? –

+0

@JosephGarvin Quando il bit più significativo dell'additivo è inferiore al bit meno significativo del totale, l'arrotondamento verso il basso è garantito. Altrimenti, hai ragione, il punto dal round al più vicino è quello di prevenire tale pregiudizio.(L'eliminazione totale del bias richiede una distribuzione lineare dei numeri, tuttavia, e molti set di dati hanno invece una distribuzione esponenziale o comunque non uniforme.) Un programma ben progettato non dovrebbe mai nemmeno avvicinarsi all'overflow (all'infinito); Penso che sarebbe un bug avere "INFINITY" all'interno del margine di errore. – Potatoswatter

12

Si potrebbe desiderare di guardare il Kahan Summation Algorithm - non è esattamente che cosa avete bisogno qui, ma si risolve un problema molto simile e si può essere in grado di adattarsi alle proprie esigenze.

+0

Molto interessante, +1. Tuttavia, che cesserà di essere efficace, il totale parziale è abbastanza grande che aggiungere qualsiasi errore aggiunge effettivamente 0. A quel punto, l'accumulatore di errori inizia a crescere rapidamente, fino a perdere anche la precisione. Quindi torna al punto 1. – Potatoswatter

+0

+1, decisamente cool. Tendo ad essere d'accordo con Potatoswatter che perderemo ancora precisione con la crescita del numero di punti dati. –

2

Se le altre soluzioni non funzionano si potrebbe indagare la Bignum library

"GMP è una libreria gratuita per arbitraria aritmetica di precisione, che operano su interi con segno, numeri razionali e numeri in virgola mobile. Non c'è limite pratico alla precisione eccetto quelle implicite dalla memoria disponibile nella macchina GMP funziona. GMP ha un ricco set di funzioni e le funzioni hanno un'interfaccia regolare. "

+0

Anche a me piace questo. Alla fine ho deciso che preferirei non aggiungere alcun overhead extra per una singola variabile. –

-1

Ciò che sembra essere una media mobile esponenziale. Questo pesa più tardi gli errori del punto dati più pesantemente di quelli precedenti. Ciò di cui hai bisogno è una media lineare. Media i tuoi dati in blocchi di 1 milione, quindi prendi la media di quei blocchi. Potresti farlo anche a più livelli. Questo pondererà tutti i punti di errore allo stesso modo.

Problemi correlati