2011-01-24 3 views
24

Sono in esecuzione test NUnit per valutare alcuni dati di test noti e risultati calcolati. I numeri sono in virgola mobile a virgola mobile quindi non mi aspetto che siano esattamente uguali, ma non sono sicuro di come trattarli come uguali per una determinata precisione.Valutare se due doppi sono uguali in base a una determinata precisione, non entro una certa tolleranza fissa

In NUnit possiamo confrontare con una tolleranza fissa:

double expected = 0.389842845321551d; 
double actual = 0.38984284532155145d; // really comes from a data import 
Expect(actual, EqualTo(expected).Within(0.000000000000001)); 

e che funziona bene per i numeri sotto lo zero, ma, come i numeri crescono la tolleranza ha davvero bisogno di essere cambiato così abbiamo sempre a cuore lo stesso numero di cifre di precisione.

In particolare, questo test fallisce:

double expected = 1.95346834136148d; 
double actual = 1.9534683413614817d; // really comes from a data import 
Expect(actual, EqualTo(expected).Within(0.000000000000001)); 

e, naturalmente, i numeri più grandi non riescono con la tolleranza ..

double expected = 1632.4587642911599d; 
double actual = 1632.4587642911633d; // really comes from a data import 
Expect(actual, EqualTo(expected).Within(0.000000000000001)); 

Qual è il modo corretto di valutare due numeri in virgola mobile sono uguali con una data precisione ? C'è un modo integrato per farlo in NUnit?

risposta

11

da MSDN:

Per default, un valore doppio contiene 15 cifre decimali di precisione, anche se viene mantenuto un massimo di 17 cifre internamente.

Supponiamo 15, quindi.

Quindi, potremmo dire che vogliamo che la tolleranza sia nella stessa misura.

Quante cifre precise abbiamo dopo il punto decimale? Abbiamo bisogno di conoscere la distanza della cifra più significativa dal punto decimale, giusto?La grandezza. Possiamo ottenere questo con un Log10.

Quindi dobbiamo dividere 1 per 10^precisione per ottenere un valore attorno alla precisione che vogliamo.

Ora, è necessario fare di più casi di test di me, ma questo sembra funzionare:

double expected = 1632.4587642911599d; 
    double actual = 1632.4587642911633d; // really comes from a data import 

    // Log10(100) = 2, so to get the manitude we add 1. 
    int magnitude = 1 + (expected == 0.0 ? -1 : Convert.ToInt32(Math.Floor(Math.Log10(expected)))); 
    int precision = 15 - magnitude ; 

    double tolerance = 1.0/Math.Pow(10, precision); 

    Assert.That(expected, Is.EqualTo(actual).Within(tolerance)); 

È tardi - ci potrebbe essere un Gotcha qui. L'ho provato contro i tuoi tre set di dati di test e passati tutti. La modifica di in 16 - magnitude ha causato il fallimento del test. Impostandolo su 14 - magnitude, ovviamente, si è verificato un errore mentre la tolleranza era maggiore.

+0

Questa è un'ottima soluzione, ma genera un'eccezione quando si confrontano da '0.0' a' 0.0'. Tuttavia, una semplice condizione 'if' attorno a' expected' si occuperebbe di ciò. –

+0

Ah si. Pensavo che ci sarebbe stato qualcosa. :) Ho aggiornato la mia risposta per tener conto di questo. – Brett

+0

ha funzionato molto bene. Ho eseguito 105 test unitari, circa la metà positivi e metà negativi, e tutti tranne 8 passati. La risposta di Michael Borgwardt ha superato e ha fallito esattamente gli stessi test. –

5

Come convertire gli articoli in stringa e confrontare le stringhe?

string test1 = String.Format("{0:0.0##}", expected); 
string test2 = String.Format("{0:0.0##}", actual); 
Assert.AreEqual(test1, test2); 
+1

Ho pensato a questo e sono d'accordo che avrebbe funzionato per convertire in stringhe. Non come hai detto esattamente, vorrai convertire in stringhe usando la precisione completa disponibile per ogni valore e poi troncarle alla stessa lunghezza. Tuttavia, spero in un risultato matematicamente appropriato, qualcosa di un po 'meno hacky. –

3

Non so se c'è un modo integrato di farlo con NUnit, ma vorrei suggerire moltiplicando ciascun galleggiante dal 10x la precisione che stai cercando, memorizzare i risultati come anela, e il confronto i due si allungano l'un l'altro.
Per esempio:

double expected = 1632.4587642911599d; 
double actual = 1632.4587642911633d; 
//for a precision of 4 
long lActual = (long) 10000 * actual; 
long lExpected = (long) 10000 * expected; 

if(lActual == lExpected) { // Do comparison 
    // Perform desired actions 
} 
+1

Questo è matematicamente identico all'utilizzo di una tolleranza, viene applicato solo in un modo diverso. –

+0

Ma questo è molto più leggibile e più semplice –

0

Questa è una rapida idea, ma come su di loro spostando verso il basso fino a quando non sono sotto lo zero? Dovrebbe essere qualcosa come num/(10^ceil(log10(num))). . . non sono sicuro di come funzionerebbe, ma è un'idea.

1632.4587642911599/(10^ceil(log10(1632.4587642911599))) = 0.16324587642911599 
+0

Questo sembra almeno un buon inizio. Che effetto ha questo calcolo sulla precisione? c'è una perdita di precisione? Ovviamente non mi interessa la piena precisione di entrambi i valori comparabili, ma se perdiamo la precisione in entrambi, allora il confronto potrebbe produrre alcuni falsi positivi. –

8

Questo è quello che mi è venuta per The Floating-Point Guide (codice Java, ma dovrebbe tradursi facilmente, e viene fornito con una suite di test, che si ha realmente realmente bisogno):

public static boolean nearlyEqual(float a, float b, float epsilon) 
{ 
    final float absA = Math.abs(a); 
    final float absB = Math.abs(b); 
    final float diff = Math.abs(a - b); 

    if (a * b == 0) { // a or b or both are zero 
     // relative error is not meaningful here 
     return diff < (epsilon * epsilon); 
    } else { // use relative error 
     return diff/(absA + absB) < epsilon; 
    } 
} 

La domanda davvero difficile è cosa fare quando uno dei numeri da confrontare è zero. La migliore risposta potrebbe essere che tale confronto dovrebbe sempre considerare il significato del dominio dei numeri confrontati piuttosto che cercare di essere universale.

+0

@ Michael Borgwardt, che cos'è esattamente epsilon? cosa fa un valore di 0,0001 in realtà uomo nei confronti? Attraverso il test posso trovare valori appropriati per epsilon che funzionano per i miei dati di test (sia true quando previsto che false quando previsto), ma non sono chiaro al 100% su questo parametro. –

+2

@ Samuel: epsilon è un margine di errore relativo, vale a dire epsilon di 0,01 significa che la differenza tra i 2 valori deve essere inferiore a circa l'1%. Ma se un valore è 0, questo diventa privo di significato, quindi in quel caso ho bisogno che l'altro valore sia più piccolo di epsilon al quadrato. Questo è abbastanza arbitrario; quindi la conclusione che una funzione di confronto universalmente utile potrebbe non esistere. –

+0

@ Michael Bordwardt, grazie, questa è una buona spiegazione. Capisco e sono d'accordo con la tua affermazione su una funzione di confronto universalmente utile, ma sfortunatamente è per una libreria, non per una specifica app aziendale, quindi sto cercando qualcosa che sia il più universale possibile. Sto ancora facendo altri test, sia la soluzione che la soluzione Bretts (più semplice, meno pulita) sembrano funzionare negli stessi casi e fallire negli stessi casi. –

0

stai:

const double significantFigures = 10; 
Assert.AreEqual(Actual/Expected, 1.0, 1.0/Math.Pow(10, significantFigures)); 
+0

Ovviamente se il valore atteso è zero o vicino ad esso, dovrai codificare un caso speciale. Quante cifre significative ha zero? Zero o infinito? – Pughjl