2010-08-06 15 views
16

Sto scrivendo test di unità che verificano i calcoli in un database e c'è un sacco di arrotondamenti e troncamenti e roba che significa che a volte le cifre sono leggermente fuori.Come faccio a sapere se due variabili sono approssimativamente uguali?

Durante la verifica, mi sto trovando un sacco di volte in cui le cose passeranno, ma dicono di non riuscire - per esempio, la cifra sarà 1 e sto ottenendo 0,999,999 mila

Voglio dire, ho potuto solo giro tutto in un intero, ma dal momento che sto usando un sacco di campioni randomizzati, alla fine ho intenzione di ottenere qualcosa di simile

10,5 10,4999999999

sta per arrotondare a 10, l'altro round per 11

Come dovrei risolvere questo problema dove ho bisogno che qualcosa sia approssimativamente corretto?

+1

Confronto di numeri in virgola mobile, edizione 2012: https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ - non in versione C# si otterrà tutto ciò che si ottiene necessario –

risposta

35

Definire un valore di tolleranza (aka un 'Epsilon'), per esempio, 0,00001, e quindi utilizzare per confrontare la differenza in questo modo:

if (Math.Abs(a - b) < epsilon) 
{ 
    // Values are within specified tolerance of each other.... 
} 

[Si potrebbe usare Double.Epsilon ma si dovrà utilizzare un fattore moltiplicatore.]

Meglio ancora, scrivere un metodo di estensione per fare lo stesso. Abbiamo qualcosa come Assert.AreSimiliar(a,b) nei nostri test di unità.

+8

Microsoft fornisce un sovraccarico della classe Assert.AreEqual che fornisce un delta. 'public static void AreEqual (double expected, double actual, double delta);' Sembra che sia stato in circolazione almeno da Visual Studio 2005 https://msdn.microsoft.com/en-us/library/ms243458(v=vs. 80) .aspx – user2023861

+0

NUnit fornisce anche un sovraccarico al loro metodo Assert.AreEqual che consente di fornire un delta. – BrettMStory

14

È possibile fornire una funzione che include un parametro per una differenza accettabile tra due valori. Per esempio

// close is good for horseshoes, hand grenades, nuclear weapons, and doubles 
static bool CloseEnoughForMe(double value1, double value2, double acceptableDifference) 
{ 
    return Math.Abs(value1 - value2) <= acceptableDifference; 
} 

E poi chiamare

double value1 = 24.5; 
double value2 = 24.4999; 

bool equalValues = CloseEnoughForMe(value1, value2, 0.001); 

Se si voleva essere un po 'professionale a questo proposito, si potrebbe chiamare la funzione ApproximatelyEquals o qualcosa del genere.

static bool ApproximatelyEquals(this double value1, double value2, double acceptableDifference) 
+5

+1 per una risposta corretta, +1 per suggerire un'estensione, -1 per non essere abbastanza snob per utilizzare una lettera greca. :-) –

+8

Lo chiamerei 'CloseEnoughForGovernmentWork()', personalmente – kyoryu

3

Un modo per confrontare i numeri in virgola mobile è confrontare quante rappresentazioni in virgola mobile che li separano. Questa soluzione è indifferente alla dimensione dei numeri e quindi non devi preoccuparti delle dimensioni di "epsilon" menzionate in altre risposte.

Una descrizione dell'algoritmo può essere trovata here (la funzione AlmostEqual2sComplement alla fine) ed ecco la mia versione C# di esso.

UPDATE: Il collegamento fornito non è aggiornato. La nuova versione, che comprende alcuni miglioramenti e correzioni di bug è here

public static class DoubleComparerExtensions 
{ 
    public static bool AlmostEquals(this double left, double right, long representationTolerance) 
    { 
     long leftAsBits = left.ToBits2Complement(); 
     long rightAsBits = right.ToBits2Complement(); 
     long floatingPointRepresentationsDiff = Math.Abs(leftAsBits - rightAsBits); 
     return (floatingPointRepresentationsDiff <= representationTolerance); 
    } 

    private static unsafe long ToBits2Complement(this double value) 
    { 
     double* valueAsDoublePtr = &value; 
     long* valueAsLongPtr = (long*)valueAsDoublePtr; 
     long valueAsLong = *valueAsLongPtr; 
     return valueAsLong < 0 
      ? (long)(0x8000000000000000 - (ulong)valueAsLong) 
      : valueAsLong; 
    } 
} 

Se vuoi confrontare galleggianti, cambia tutto double a float, tutto long a int e 0x8000000000000000 a 0x80000000.

Con il parametro representationTolerance è possibile specificare l'entità di tolleranza tollerata. Un valore più alto significa che è accettato un errore più grande. Normalmente utilizzo il valore 10 come predefinito.

+0

Che ne dici di confrontare zero e un valore negativo molto piccolo. Sembra che non funzioni. per esempio. confronta 0 e -1.1102230246251565E-16 –

+0

Il link fornito non è aggiornato. La nuova posizione è https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ –

+0

Non capisco perché sottrassi valori negativi da '0x800 ...' Potrebbe per favore, approfondisci questo? – m93a

8

Non ho verificato in quale versione di MS Test sono stati aggiunti ma in v10.0.0.0 i metodi Assert.AreEqual hanno sovraccarichi che accettano un parametro delta e fanno un confronto approssimativo.

I.e.

// 
// Summary: 
//  Verifies that two specified doubles are equal, or within the specified accuracy 
//  of each other. The assertion fails if they are not within the specified accuracy 
//  of each other. 
// 
// Parameters: 
// expected: 
//  The first double to compare. This is the double the unit test expects. 
// 
// actual: 
//  The second double to compare. This is the double the unit test produced. 
// 
// delta: 
//  The required accuracy. The assertion will fail only if expected is different 
//  from actual by more than delta. 
// 
// Exceptions: 
// Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException: 
//  expected is different from actual by more than delta. 
public static void AreEqual(double expected, double actual, double delta); 
1

La questione stava chiedendo come affermare qualcosa era quasi uguali in unit testing. Si asserisce che qualcosa è quasi uguale utilizzando la funzione integrata Assert.AreEqual. Ad esempio:

Assert.AreEqual(expected: 3.5, actual : 3.4999999, delta:0.1);

Questo test passerà. Problema risolto e senza dover scrivere la propria funzione!

Problemi correlati