Signification de l'argument delta ou epsilon de assertEquals pour les valeurs doubles

191

J'ai une question sur JUnit assertEqualspour tester les doublevaleurs. En lisant le document de l' API, je peux voir:

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

Obsolète. Utilisez assertEquals(double expected, double actual, double delta)plutôt.

(Remarque: dans les anciennes versions de documentation, le paramètre delta est appelé epsilon)

Que signifie le paramètre delta(ou epsilon)?

Édipo Féderle
la source

Réponses:

205

Epsilon est la valeur par laquelle les 2 nombres peuvent être décalés. Donc, il affirmera vrai tant queMath.abs(expected - actual) < epsilon

Jberg
la source
3
Alors, quelle valeur dois-je passer pour epsilon?
emeraldhieu
15
@ Emerald214 la quantité de précision. Si vous voulez affirmer qu'une valeur double est 0D, epsilon serait 0 (précision de 100%, sans exception). Si vous voulez une marge d'erreur (par exemple pour les degrés), vous pouvez définir epsilon sur 1, ce qui signifie que, par exemple, 64,2 ° équivaut à 64,8 ° (puisque abs (64,8-64,2) <1)
Pieter De Bie
3
La documentation dit, "delta - le delta maximum entre prévu et réel pour lequel les deux nombres sont toujours considérés comme égaux." Je pense donc que cela ne devrait <=pas être le cas <.
Andrew Cheong le
En regardant le code, je vois qu'il appelle la méthode doubleIsDifferent(pour comparer des valeurs doubles) et qu'il retourne Math.abs(d1 - d2) > delta. Donc, si la différence entre d1 et d2 est supérieure à delta, cela signifie que les valeurs sont différentes et renverront true. Il renverra false si les valeurs sont considérées comme égales. Cette méthode est appelée directement dans assertEquals et si elle renvoie true, l'assertEquals appellera failNotEqualset le résultat du test sera un échec.
anthomaxcool
1
@jbert Quelqu'un peut-il me dire quelle serait une valeur double epsilon typique si je travaillais simplement avec une moyenne de nombreux nombres ou des écarts-types?
simgineer
124

De quelle version de JUnit s'agit-il? Je n'ai jamais vu que delta, pas epsilon - mais c'est un problème secondaire!

Depuis le javadoc JUnit :

delta - le delta maximum entre prévu et réel pour lequel les deux nombres sont toujours considérés comme égaux.

C'est probablement exagéré, mais j'utilise généralement un très petit nombre, par exemple

private static final double DELTA = 1e-15;

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

Si vous utilisez des assertions hamcrest , vous pouvez simplement utiliser le standard equalTo()avec deux doubles (il n'utilise pas de delta). Cependant, si vous voulez un delta, vous pouvez simplement utiliser closeTo()(voir javadoc ), par exemple

private static final double DELTA = 1e-15;

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

Pour info, la prochaine JUnit 5 rendra également le delta facultatif lors d'un appel assertEquals()avec deux doubles. La mise en œuvre (si vous êtes intéressé) est:

private static boolean doublesAreEqual(double value1, double value2) {
    return Double.doubleToLongBits(value1) == Double.doubleToLongBits(value2);
}
James Bassett
la source
57

Les calculs en virgule flottante ne sont pas exacts - il y a souvent des erreurs d'arrondi et des erreurs dues à la représentation. (Par exemple, 0,1 ne peut pas être représenté exactement en virgule flottante binaire.)

Pour cette raison, comparer directement deux valeurs à virgule flottante pour l'égalité n'est généralement pas une bonne idée, car elles peuvent être légèrement différentes, en fonction de la façon dont elles ont été calculées.

Le "delta", comme on l'appelle dans les javadocs JUnit , décrit la quantité de différence que vous pouvez tolérer dans les valeurs pour qu'elles soient toujours considérées comme égales. La taille de cette valeur dépend entièrement des valeurs que vous comparez. Lorsque je compare des doubles, j'utilise généralement la valeur attendue divisée par 10 ^ 6.

mdma
la source
11

Le fait est que deux doubles peuvent ne pas être exactement égaux en raison de problèmes de précision inhérents aux nombres à virgule flottante. Avec cette valeur delta, vous pouvez contrôler l'évaluation de l'égalité basée sur un facteur d'erreur.

De plus, certaines valeurs à virgule flottante peuvent avoir des valeurs spéciales telles que NAN et -Infinity / + Infinity qui peuvent influencer les résultats.

Si vous avez vraiment l'intention de comparer que deux doubles sont exactement égaux, il est préférable de les comparer comme une longue représentation

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

Ou

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

Ce qui peut prendre en compte ces nuances.

Je n'ai pas approfondi la méthode Assert en question, mais je ne peux que supposer que la précédente était obsolète pour ce genre de problèmes et que la nouvelle les prend en compte.

Edwin Dalorzo
la source
2

Epsilon est une différence entre expectedet des actualvaleurs que vous pouvez accepter en pensant qu'elles sont égales. Vous pouvez définir .1par exemple.

Constantiner
la source
2

Notez que si vous ne faites pas de maths, il n'y a rien de mal à affirmer des valeurs en virgule flottante exactes. Par exemple:

public interface Foo {
    double getDefaultValue();
}

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

Dans ce cas, vous voulez vous assurer que c'est vraiment MIN_VALUE, pas zéro ou -MIN_VALUEou MIN_NORMALou une autre très petite valeur. Tu peux dire

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

mais cela vous donnera un avertissement de dépréciation. Pour éviter cela, vous pouvez appeler à la assertEquals(Object, Object)place:

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

Et, si vous voulez vraiment avoir l'air intelligent:

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

Ou vous pouvez simplement utiliser des assertions de style Hamcrest couramment:

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

Si la valeur que vous vérifiez ne venez de faire un peu de maths, bien que, utilisez le epsilon.

David Moles
la source
7
Si vous voulez vérifier exactement égal, définissez epsilon sur 0.0 - la variante Object n'est pas requise.
Mel Nicholson
-2
Assert.assertTrue(Math.abs(actual-expected) == 0)
Prakash
la source
Lorsque vous utilisez des nombres à virgule flottante (comme float ou double), cela ne fonctionnera pas de manière fiable. Vous voudrez peut-être examiner comment les nombres à virgule flottante en java sont stockés et comment les opérations arithmétiques fonctionnent sur eux. (spoiler: attendez-vous à des erreurs d'arrondi!)
Attila