2010-02-12 5 views
6

Sto lavorando a un calcolo scientifico & progetto di visualizzazione in C# /. NET, e usiamo double s per rappresentare tutte le quantità fisiche. Dal momento che i numeri in virgola mobile sono sempre un po 'di arrotondamento, abbiamo metodi semplici per fare confronti di uguaglianza, come ad esempio:Gestione degli errori in virgola mobile in .NET

static double EPSILON = 1e-6; 

bool ApproxEquals(double d1, double d2) { 
    return Math.Abs(d1 - d2) < EPSILON; 
} 

Piuttosto standard.

Tuttavia, ci troviamo costantemente a dover regolare la grandezza di EPSILON quando incontriamo situazioni in cui l'errore di quantità "uguali" è maggiore di quanto avevamo previsto. Ad esempio, se si moltiplicano 5 grandi double s e poi si dividono 5 volte, si perde molta precisione. È arrivato al punto in cui non siamo in grado di rendere EPSILON troppo più grande o ci darà falsi positivi, ma otteniamo anche falsi negativi.

In generale, il nostro approccio è stato quello di cercare altri algoritmi numericamente stabili con cui lavorare, ma il programma è molto computazionale e c'è solo così tanto che siamo stati in grado di fare.

Qualcuno ha qualche buona strategia per affrontare questo problema? Ho esaminato un po 'il tipo Decimal, ma sono preoccupato per le prestazioni e in realtà non ne so abbastanza per sapere se risolverebbe il problema o lo oscurasse solo. Sarei disposto ad accettare un hit moderato (ad esempio 2x) andando a Decimal se risolverà questi problemi, ma le prestazioni sono sicuramente una preoccupazione e poiché il codice è per lo più limitato dall'aritmetica in virgola mobile, non credo è una preoccupazione irragionevole. Ho visto persone citando una differenza di 100x, che sarebbe sicuramente inaccettabile.

Inoltre, il passaggio a Decimal ha altre complicazioni, come la mancanza generale di supporto nella libreria Math, quindi dovremmo scrivere la nostra funzione radice quadrata, ad esempio.

Qualche consiglio?

EDIT: a proposito, il fatto che io stia utilizzando un epsilon costante (invece di un confronto relativo) non è il punto della mia domanda. L'ho appena messo lì come esempio, non è in realtà uno snippit del mio codice. Passare a un confronto relativo non farebbe la differenza per la domanda, perché il problema deriva dalla perdita di precisione quando i numeri diventano molto grandi e poi di nuovo piccoli. Ad esempio, potrei avere un valore 1000 e quindi eseguo una serie di calcoli su di esso che dovrebbe risultare esattamente nello stesso numero, ma a causa della perdita di precisione ho effettivamente 1001. Se poi vado a confrontare quei numeri, non lo fa Non importa se uso un confronto relativo o assoluto (a patto che abbia definito i confronti in modo significativo per il problema e la scala).

In ogni caso, come suggerito da Mitch Wheat, il riordino degli algoritmi ha aiutato i problemi.

risposta

4

Questo non è un problema esclusivo di .NET. La strategia per ridurre la perdita di precisione consiste nel riordinare i calcoli in modo da moltiplicare le grandi quantità per piccole quantità e aggiungere/sottrarre quantità di dimensioni simili (senza cambiare la natura del calcolo, ovviamente).

Nell'esempio, anziché moltiplicare 5 grandi quantità insieme e quindi dividere per 5 grandi quantità, riordinare per dividere ciascuna grande quantità per uno dei divisori e quindi moltiplicare questi 5 risultati parziali.

Di interesse?(Se non lo avete già letto): What Every Computer Scientist Should Know About Floating-Point Arithmetic

+0

@nobugz: se la senti forte, ripristina la tua risposta e rimuoverò il downvote. –

+0

@ Mark ad alte prestazioni: questo commento era destinato all'OP? –

+0

Beh, ho esaminato i nostri algoritmi e ho fatto un po 'di riordinamento come suggerito, e sembra aver aiutato. Ho iniziato a lavorare su quell'articolo, anche se non è esattamente una lettura leggera! Grazie per l'aiuto. –

1

A causa dei numeri reali modo in cui sono tipicamente rappresentati, Si può fare questo in C (e probabilmente in non sicuro C#):

if (llabs(*(long long)&x - *(long long)&y) <= EPSILON) { 
    // Close enough 
} 

Questo è ovviamente non -portabile e probabilmente una cattiva idea, ma ha il significativo vantaggio di indipendenza dalla scala. Cioè, EPSILON può essere una piccola costante come 1, 10 o 100 (a seconda della tolleranza desiderata) e gestirà correttamente gli errori di arrotondamento proporzionali indipendentemente dall'esponente.

Disclaimer: Questa è una mia invenzione privata, e non è stata controllata da nessuno con un indizio (come, per esempio, un matematico con uno sfondo in discreta aritmetica).

+0

Lasciatemi riformulare quello: come è meglio della tecnica originale di tolleranza in virgola mobile (epsilon)? –

+0

Epsilon deve normalmente essere ridimensionato in proporzione alla grandezza dei numeri che ci si aspetta di gestire, il che lo rende vulnerabile alle sorprese nella grandezza dei dati reali. Questo è precisamente il problema indicato nella domanda. Confrontando la rappresentazione intera dei numeri non è vulnerabile a tali problemi di ridimensionamento (si noti che la scelta di EPSILON nella mia risposta è determinata dalla tolleranza desiderata, non dalla scala prevista degli input). La risposta di @John Knoeller è la migliore risposta sicura e portatile, ma è più costosa (anche se probabilmente marginalmente). –

2

La tua migliore risposta è sempre algoritmi migliori, ovviamente. Ma mi sembra che se i tuoi valori non sono tutti entro un paio di ordini di grandezza di 1, l'utilizzo di un epsilon fisso non è una buona strategia. Quello che vuoi fare invece è assicurarti che i valori siano uguali all'interno di una ragionevole precisione.

// are values equal to within 12 (or so) digits of precision? 
// 
bool ApproxEquals(double d1, double d2) { 
    return Math.Abs(d1 - d2) < (Math.Abs(d1) * 1e-12); 
} 

Se questo fosse C++, allora si potrebbe anche tirare alcuni trucchi per confrontare la mantissa ed esponente separatamente, ma non riesco a pensare a un modo per farlo in modo sicuro nel codice unmanged.

+0

Come ho aggiunto in una modifica alla domanda, questo in realtà non risolve la domanda che ho avuto. Ma è vero che un confronto relativo è appropriato quando i confronti vengono fatti su numeri con ordini di grandezza variabili. –