Significato dell'argomento epsilon di assertEquals per doppi valori


187

Ho una domanda su junit assertEqualsper testare valori doppi. Leggendo il documento API posso vedere:

@Deprecated
public static void assertEquals(double expected, double actual)

Deprecato. Utilizzare invece assertEquals (doppio previsto, doppio effettivo, doppio epsilon)

Cosa significa il epsilonvalore? (Epsilon è una lettera dell'alfabeto greco, giusto?).

Qualcuno può spiegarmi come usarlo?

Risposte:


198

Epsilon è il valore con cui i 2 numeri possono essere disattivati. Quindi affermerà vero finchéMath.abs(expected - actual) < epsilon


3
Quindi quale valore devo passare come epsilon?
emeraldhieu,

15
@ Emerald214 la quantità di precisione. Se vuoi affermare che un doppio valore è 0D epsilon sarebbe 0 (precisione del 100%, senza eccezioni). Se vuoi un margine di errore (diciamo per gradi) puoi impostare epsilon su 1, il che significa che, ad esempio, 64.2 ° è uguale a 64.8 ° (poiché abs (64.8-64.2) <1)
Pieter De Bie

3
La documentazione dice "delta - il delta massimo tra atteso e reale per il quale entrambi i numeri sono ancora considerati uguali". Quindi penso che dovrebbe essere un <=non <.
Andrew Cheong,

Guardando il codice, vedo che chiama il metodo doubleIsDifferent(per confrontare valori doppi) e restituisce Math.abs(d1 - d2) > delta. Quindi, se la differenza tra d1 e d2 è maggiore di delta, ciò significa che i valori sono diversi e restituiranno true. Restituirà falso se i valori sono considerati uguali. Tale metodo viene chiamato in assertEquals direttamente e se restituisce true, assertEquals chiamerà failNotEqualse il risultato del test sarà un errore.
anthomaxcool,

1
@jbert Qualcuno può consigliare quale sarebbe un tipico doppio valore epsilon se stessi lavorando con la media di molti numeri o facendo deviazioni standard?
ingegnere

121

Quale versione di JUnit è questa? Ho sempre visto delta, non epsilon - ma questo è un problema secondario!

Dal javadoc di JUnit :

delta: il delta massimo tra atteso e effettivo per il quale entrambi i numeri sono ancora considerati uguali.

Probabilmente è eccessivo, ma di solito uso un numero molto piccolo, ad es

private static final double DELTA = 1e-15;

@Test
public void testDelta(){
    assertEquals(123.456, 123.456, DELTA);
}

Se stai usando affermazioni più critiche , puoi semplicemente usare lo standard equalTo()con due doppie (non usa un delta). Tuttavia, se vuoi un delta, puoi semplicemente usare closeTo()(vedi javadoc ), ad es

private static final double DELTA = 1e-15;

@Test
public void testDelta(){
    assertThat(123.456, equalTo(123.456));
    assertThat(123.456, closeTo(123.456, DELTA));
}

Cordiali saluti il prossimo JUnit 5 sarà anche rendere il delta opzionale quando si chiama assertEquals()con due doppie. L' implementazione (se sei interessato) è:

private static boolean doublesAreEqual(double value1, double value2) {
    return Double.doubleToLongBits(value1) == Double.doubleToLongBits(value2);
}

57

I calcoli in virgola mobile non sono esatti: spesso si verificano errori di arrotondamento ed errori dovuti alla rappresentazione. (Ad esempio, 0.1 non può essere rappresentato esattamente in virgola mobile binaria.)

Per questo motivo, confrontare direttamente due valori in virgola mobile per l'uguaglianza di solito non è una buona idea, perché possono essere diversi da una piccola quantità, a seconda di come sono stati calcolati.

Il "delta", come viene chiamato nel javadocs di JUnit , descrive la quantità di differenza che puoi tollerare nei valori perché possano essere considerati uguali. La dimensione di questo valore dipende interamente dai valori che stai confrontando. Quando si confrontano i doppi, di solito uso il valore atteso diviso per 10 ^ 6.


11

Il fatto è che due doppie potrebbero non essere esattamente uguali a causa di problemi di precisione inerenti ai numeri in virgola mobile. Con questo valore delta è possibile controllare la valutazione dell'uguaglianza in base a un fattore di errore.

Inoltre, alcuni valori in virgola mobile possono avere valori speciali come NAN e -Infinity / + Infinity che possono influenzare i risultati.

Se hai davvero intenzione di confrontare che due doppi sono esattamente uguali, è meglio confrontarli come una rappresentazione lunga

Assert.assertEquals(Double.doubleToLongBits(expected), Double.doubleToLongBits(result));

O

Assert.assertEquals(0, Double.compareTo(expected, result));

Che può prendere in considerazione queste sfumature.

Non ho approfondito il metodo Assert in questione, ma posso solo supporre che il precedente sia stato deprecato per questo tipo di problemi e quello nuovo li tiene in considerazione.


2

Epsilon è una differenza tra expectede actualvalori che puoi accettare pensando che siano uguali. È possibile impostare .1ad esempio.


2

Nota che se non stai facendo matematica, non c'è niente di sbagliato nell'affermare valori esatti in virgola mobile. Per esempio:

public interface Foo {
    double getDefaultValue();
}

public class FooImpl implements Foo {
    public double getDefaultValue() { return Double.MIN_VALUE; }
}

In questo caso, vuoi assicurarti che sia davvero MIN_VALUE, non zero o -MIN_VALUEo MIN_NORMALo qualche altro valore molto piccolo. Si può dire

double defaultValue = new FooImpl().getDefaultValue();
assertEquals(Double.MIN_VALUE, defaultValue);

ma questo ti darà un avviso di deprecazione. Per evitarlo, puoi chiamare assertEquals(Object, Object)invece:

// really you just need one cast because of autoboxing, but let's be clear
assertEquals((Object)Double.MIN_VALUE, (Object)defaultValue);

E, se vuoi davvero sembrare intelligente:

assertEquals(
    Double.doubleToLongBits(Double.MIN_VALUE), 
    Double.doubleToLongBits(defaultValue)
);

Oppure puoi semplicemente usare le affermazioni in stile fluente di Hamcrest:

// equivalent to assertEquals((Object)Double.MIN_VALUE, (Object)defaultValue);
assertThat(defaultValue, is(Double.MIN_VALUE));

Se il valore si sta controllando non vieni da fare un po 'di matematica, però, utilizzare l'Epsilon.


7
Se si desidera verificare che sia esattamente uguale, impostare epsilon su 0,0 - la variante di oggetto non è richiesta.
Mel Nicholson,

-2
Assert.assertTrue(Math.abs(actual-expected) == 0)

Quando si usano numeri in virgola mobile (come float o double), questo non funzionerà in modo affidabile. Potresti voler esaminare come sono memorizzati i numeri in virgola mobile in Java e come funzionano le operazioni aritmetiche su di essi. (spoiler: aspettatevi alcuni errori di arrotondamento!)
Attila,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.