Znaczenie argumentu epsilon assertEquals dla podwójnych wartości

187

Mam pytanie o junit assertEqualsdo testowania podwójnych wartości. Czytając dokument API, widzę:

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

Przestarzałe. Zamiast tego należy użyć assertEquals (podwójne oczekiwane, podwójne rzeczywiste, podwójne epsilon)

Co oznacza epsilonwartość? (Epsilon to litera alfabetu greckiego, prawda?).

Czy ktoś może mi wyjaśnić, jak go używać?

Édipo Féderle
źródło

Odpowiedzi:

198

Epsilon to wartość, o którą 2 numery mogą być wyłączone. Tak długo będzie to potwierdzaćMath.abs(expected - actual) < epsilon

jberg
źródło
3
Jaką wartość powinienem przekazać jako epsilon?
emeraldhieu
15
@ Emerald214 ilość precyzji. Jeśli chcesz stwierdzić, że podwójna wartość to 0D, epsilon wyniósłby 0 (100% dokładności, bez wyjątków). Jeśli chcesz margines błędu (powiedzmy dla stopni), możesz ustawić epsilon na 1, co oznacza, że ​​na przykład 64,2 ° jest takie samo jak 64,8 ° (ponieważ abs (64,8-64,2) <1)
Pieter De Bie
3
Dokumentacja mówi: „delta - maksymalna delta między oczekiwaną a rzeczywistą, dla której obie liczby są nadal uważane za równe”. Myślę więc, że to <=nie powinno być <.
Andrew Cheong
Patrząc na kod, widzę, że wywołuje metodę doubleIsDifferent(do porównywania podwójnych wartości) i zwraca Math.abs(d1 - d2) > delta. Więc jeśli różnica między d1 i d2 jest większa niż delta, oznacza to, że wartości są różne i zwróci true. Zwróci false, jeśli wartości zostaną uznane za równe. Ta metoda jest wywoływana bezpośrednio w assertEquals, a jeśli zwróci true, assertEquals zadzwoni, failNotEqualsa wynik testu zakończy się niepowodzeniem.
anthomaxcool,
1
@jbert Czy ktoś może doradzić, jaka byłaby typowa podwójna wartość epsilon, gdybym tylko pracował z uśrednianiem wielu liczb lub robiłem standardowe odchylenia?
simgineer
121

Która to wersja JUnit? Widziałem tylko deltę, a nie epsilon - ale to poboczny problem!

Z JUnit javadoc :

delta - maksymalna delta między oczekiwaną a rzeczywistą, dla której obie liczby są nadal uważane za równe.

To chyba przesada, ale zazwyczaj używam naprawdę małej liczby, np

private static final double DELTA = 1e-15;

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

Jeśli używasz twierdzeń hamcrest , możesz po prostu użyć standardu equalTo()z dwoma podwójnymi (nie używa delty). Jednak jeśli chcesz delty, możesz po prostu użyć closeTo()(patrz javadoc ), np

private static final double DELTA = 1e-15;

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

Do Twojej wiadomości, że nadchodzący JUnit 5 sprawi, że delta będzie również opcjonalny, gdy będziesz sprawdzaćassertEquals() z dwoma podwójnymi. Wdrożenie (jeśli jesteś zainteresowany) jest:

private static boolean doublesAreEqual(double value1, double value2) {
    return Double.doubleToLongBits(value1) == Double.doubleToLongBits(value2);
}
James Bassett
źródło
57

Obliczenia zmiennoprzecinkowe nie są dokładne - często występują błędy zaokrągleń i błędy wynikające z reprezentacji. (Na przykład 0,1 nie może być dokładnie reprezentowane w binarnym zmiennoprzecinkowym.)

Z tego powodu bezpośrednie porównywanie dwóch wartości zmiennoprzecinkowych dla równości zwykle nie jest dobrym pomysłem, ponieważ mogą się one nieznacznie różnić w zależności od sposobu ich obliczenia.

„Delta”, jak to się nazywa w javadocs JUnit , opisuje różnicę, którą można tolerować w wartościach, aby nadal były uważane za równe. Rozmiar tej wartości jest całkowicie zależny od porównywanych wartości. Porównując liczby podwójne, zwykle używam wartości oczekiwanej podzielonej przez 10 ^ 6.

MDMA
źródło
11

Chodzi o to, że dwa podwójne mogą nie być dokładnie równe z powodu problemów z precyzją związanych z liczbami zmiennoprzecinkowymi. Za pomocą tej wartości delta można kontrolować ocenę równości w oparciu o współczynnik błędu.

Również niektóre wartości zmiennoprzecinkowe mogą mieć specjalne wartości, takie jak NAN i -Infinity / + Infinity, które mogą wpływać na wyniki.

Jeśli naprawdę zamierzasz porównać, że dwa podwójne są dokładnie takie same, najlepiej porównać je jako długą reprezentację

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

Lub

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

Które mogą wziąć pod uwagę te niuanse.

Nie zagłębiłem się w omawianą metodę Assert, ale mogę jedynie założyć, że poprzednia była przestarzała dla tego rodzaju problemów, a nowa bierze je pod uwagę.

Edwin Dalorzo
źródło
2

Epsilon jest różnica między expectedi actualwartości, które można zaakceptować myśli, że są równe. Możesz ustawić .1na przykład.

Constantiner
źródło
2

Pamiętaj, że jeśli nie robisz matematyki, nie ma nic złego w zapewnianiu dokładnych wartości zmiennoprzecinkowych. Na przykład:

public interface Foo {
    double getDefaultValue();
}

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

W tym przypadku, chcesz się upewnić, że to naprawdę MIN_VALUEnie zero lub -MIN_VALUElub MIN_NORMALlub jakiś inny bardzo małą wartość. Możesz powiedzieć

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

ale dostaniesz ostrzeżenie o rezygnacji. Aby tego uniknąć, możesz assertEquals(Object, Object)zamiast tego zadzwonić :

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

A jeśli naprawdę chcesz wyglądać sprytnie:

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

Lub możesz po prostu użyć płynnych twierdzeń Hamcrest:

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

Jeśli wartość jesteś sprawdzanie ma pochodzić z jakiejś matematyki, choć użyć epsilon.

David Moles
źródło
7
Jeśli chcesz sprawdzić, czy wartość jest dokładnie równa, ustaw wartość epsilon na 0.0 - wariant Object nie jest wymagany.
Mel Nicholson,
-2
Assert.assertTrue(Math.abs(actual-expected) == 0)
Prakash
źródło
Gdy używa się liczb zmiennoprzecinkowych (takich jak liczba zmiennoprzecinkowa lub podwójna), nie zadziała to niezawodnie. Możesz sprawdzić, jak przechowywane są liczby zmiennoprzecinkowe w Javie i jak działają na nich operacje arytmetyczne. (spoiler: spodziewaj się błędów zaokrąglania!)
Attila