Significado del argumento épsilon de afirmar iguales para valores dobles

187

Tengo una pregunta sobre junit assertEqualspara probar valores dobles. Al leer el documento de API, puedo ver:

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

Obsoleto. En su lugar, utilice asirEquals (doble esperado, doble real, doble epsilon)

¿Qué significa el epsilonvalor? (Epsilon es una letra en el alfabeto griego, ¿verdad?).

¿Alguien puede explicarme cómo usarlo?

Édipo Féderle
fuente

Respuestas:

198

Epsilon es el valor por el cual los 2 números pueden estar desactivados. Por lo tanto, se afirmará como verdadero siempre queMath.abs(expected - actual) < epsilon

jberg
fuente
3
Entonces, ¿qué valor debo pasar como epsilon?
emeraldhieu
15
@ Emerald214 la cantidad de precisión. Si desea afirmar que un valor doble es 0D, épsilon sería 0 (100% de precisión, sin excepciones). Si desea un margen de error (digamos para grados), puede establecer epsilon en 1, lo que significa que, por ejemplo, 64.2 ° es lo mismo que 64.8 ° (ya que abs (64.8-64.2) <1)
Pieter De Bie
3
La documentación dice: "delta: el delta máximo entre el esperado y el real para el cual ambos números todavía se consideran iguales". Así que creo que debería ser un <=no <.
Andrew Cheong
Al mirar el código, veo que llama al método doubleIsDifferent(para comparar valores dobles) y vuelve Math.abs(d1 - d2) > delta. Entonces, si la diferencia entre d1 y d2 es mayor que delta, eso significa que los valores son diferentes y devolverán verdadero. Devolverá falso si los valores se consideran iguales. Ese método se invoca en afirmar los Equivalentes directamente y si devuelve verdadero, los invocarán failNotEqualsy el resultado de la prueba será un error.
anthomaxcool
1
@jbert ¿Alguien puede aconsejar cuál sería un valor doble de épsilon típico si solo estuviera trabajando con promediar muchos números o haciendo desviaciones estándar?
simgineer
121

¿Qué versión de JUnit es esta? Solo he visto delta, no épsilon, ¡pero ese es un problema secundario!

Desde el JUnit javadoc :

delta: el delta máximo entre el esperado y el real para el cual ambos números aún se consideran iguales.

Probablemente sea exagerado, pero normalmente uso un número muy pequeño, por ejemplo

private static final double DELTA = 1e-15;

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

Si usa aserciones Hamcrest , puede usar el estándar equalTo()con dos dobles (no usa un delta). Sin embargo, si quieres un delta, puedes usar closeTo()(ver javadoc ), por ejemplo

private static final double DELTA = 1e-15;

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

Para su información, el próximo JUnit 5 también hará que el delta sea opcional cuando llame assertEquals()con dos dobles. La implementación (si está interesado) es:

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

Los cálculos de punto flotante no son exactos: a menudo hay errores de redondeo y errores debido a la representación. (Por ejemplo, 0.1 no puede representarse exactamente en coma flotante binaria).

Debido a esto, comparar directamente dos valores de coma flotante para la igualdad generalmente no es una buena idea, ya que pueden ser diferentes en una pequeña cantidad, dependiendo de cómo se calcularon.

El "delta", como se llama en los javadocs JUnit , describe la cantidad de diferencia que puede tolerar en los valores para que se sigan considerando iguales. El tamaño de este valor depende completamente de los valores que está comparando. Al comparar dobles, generalmente uso el valor esperado dividido por 10 ^ 6.

mdma
fuente
11

La cuestión es que dos dobles pueden no ser exactamente iguales debido a problemas de precisión inherentes a los números de coma flotante. Con este valor delta puede controlar la evaluación de igualdad basada en un factor de error.

Además, algunos valores de punto flotante pueden tener valores especiales como NAN e -Infinity / + Infinity que pueden influir en los resultados.

Si realmente tiene la intención de comparar que dos dobles son exactamente iguales, es mejor compararlos como una representación larga

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

O

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

Lo que puede tener en cuenta estos matices.

No he profundizado en el método Assert en cuestión, pero solo puedo suponer que el anterior fue desaprobado para este tipo de problemas y el nuevo los tiene en cuenta.

Edwin Dalorzo
fuente
2

Epsilon es una diferencia entre expectedy actualvalores que usted puede aceptar pensando que son iguales. Puedes configurar .1por ejemplo.

Constantiner
fuente
2

Tenga en cuenta que si no está haciendo matemáticas, no hay nada de malo en afirmar valores exactos de coma flotante. Por ejemplo:

public interface Foo {
    double getDefaultValue();
}

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

En este caso, usted quiere asegurarse de que es realmente MIN_VALUE, no es cero o -MIN_VALUEo MIN_NORMALo algún otro valor muy pequeño. Puedes decir

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

pero esto te dará una advertencia de desaprobación. Para evitar eso, puede llamar en su assertEquals(Object, Object)lugar:

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

Y, si realmente quieres lucir inteligente:

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

O simplemente puede usar las afirmaciones de estilo fluido de Hamcrest:

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

Si el valor que se está comprobando lo hace viene de hacer un poco de matemáticas, sin embargo, utilizar el épsilon.

David Moles
fuente
77
Si desea verificar un valor exactamente igual, establezca epsilon en 0.0; no se requiere la variante Object.
Mel Nicholson
-2
Assert.assertTrue(Math.abs(actual-expected) == 0)
Prakash
fuente
Cuando se usan números de coma flotante (como flotante o doble), esto no funcionará de manera confiable. Es posible que desee revisar cómo se almacenan los números de coma flotante en Java y cómo funcionan las operaciones aritméticas en ellos. (spoiler: ¡espere algunos errores de redondeo!)
Attila