Значение аргумента эпсилон assertEquals для двойных значений

187

У меня есть вопрос о junit assertEqualsдля проверки двойных значений. Читая документацию по API, я вижу:

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

Устаревшее. Вместо этого используйте assertEquals (двойной ожидаемый, двойной фактический, двойной эпсилон)

Что означает epsilonзначение? (Эпсилон это буква в греческом алфавите, верно?).

Может кто-нибудь объяснить мне, как его использовать?

Эдипо Федерле
источник

Ответы:

198

Эпсилон - это значение, которым могут быть отключены 2 числа. Так будет утверждать истину до тех пор, покаMath.abs(expected - actual) < epsilon

jberg
источник
3
Так какое значение я должен передать как эпсилон?
Изумрудный
15
@ Emerald214 количество точности. Если вы хотите утверждать, что двойное значение равно 0D, эпсилон будет равен 0 (точность 100%, без исключений). Если вам нужен предел погрешности (скажем, в градусах), вы можете установить значение epsilon равным 1, что означает, например, что 64,2 ° - это то же самое, что и 64,8 ° (поскольку abs (64,8-64,2) <1)
Pieter De Bie
3
В документации сказано: «дельта - максимальная дельта между ожидаемым и фактическим, для которой оба числа все еще считаются равными». Поэтому я думаю, что это <=не так <.
Эндрю Чонг
Глядя на код, я вижу, что он вызывает метод doubleIsDifferent(для сравнения двойных значений) и возвращает Math.abs(d1 - d2) > delta. Таким образом, если разница между d1 и d2 выше дельты, это означает, что значения отличаются и вернут true. Он вернет false, если значения считаются равными. Этот метод вызывается в assertEquals непосредственно, и если он возвращает true, assertEquals будет вызывать, failNotEqualsи результатом теста будет сбой.
anthomaxcool
1
@jbert Может кто-нибудь посоветовать, каково было бы типичное двойное значение эпсилона, если бы я просто усреднял много чисел или делал стандартные отклонения?
simgineer
121

Какая версия JUnit это? Я только когда-либо видел дельту, а не эпсилон - но это побочный вопрос!

Из JUnit Javadoc :

дельта - максимальная дельта между ожидаемым и фактическим, для которой оба числа по-прежнему считаются равными.

Это, вероятно, излишне, но я обычно использую очень небольшое число, например

private static final double DELTA = 1e-15;

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

Если вы используете утверждения Hamcrest , вы можете просто использовать стандарт equalTo()с двумя двойными (он не использует дельту). Однако, если вы хотите дельту, вы можете просто использовать closeTo()(см. Javadoc ), например,

private static final double DELTA = 1e-15;

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

К вашему сведению, предстоящий JUnit 5 также сделает дельту необязательной при вызове assertEquals()с двумя двойными числами. Реализация (если вы заинтересованы) является:

private static boolean doublesAreEqual(double value1, double value2) {
    return Double.doubleToLongBits(value1) == Double.doubleToLongBits(value2);
}
Джеймс Бассетт
источник
57

Вычисления с плавающей точкой не точны - часто возникают ошибки округления и ошибки из-за представления. (Например, 0.1 не может быть точно представлено в двоичной форме с плавающей запятой.)

Из-за этого прямое сравнение двух значений с плавающей точкой на равенство обычно не очень хорошая идея, потому что они могут отличаться на небольшое количество, в зависимости от того, как они были вычислены.

«Дельта», как она называется в Javadocs JUnit , описывает величину разницы, которую вы можете допустить в значениях, чтобы они все еще считались равными. Размер этого значения полностью зависит от сравниваемых значений. При сравнении значений типа double я обычно использую ожидаемое значение, деленное на 10 ^ 6.

МДМА
источник
11

Дело в том, что два двойных не могут быть точно равными из-за проблем точности, свойственных числам с плавающей запятой. С помощью этого дельта-значения вы можете контролировать оценку равенства на основе коэффициента ошибок.

Также некоторые значения с плавающей точкой могут иметь специальные значения, такие как NAN и -Infinity / + Infinity, которые могут влиять на результаты.

Если вы действительно хотите сравнить два одинаковых числа, лучше сравнить их как длинное представление.

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

Или

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

Который может учитывать эти нюансы.

Я не углублялся в рассматриваемый метод Assert, но могу только предположить, что предыдущий был признан устаревшим для такого рода проблем, а новый действительно учитывает их.

Эдвин Далорсо
источник
2

Эпсилон разница между expectedи actualценности , которые вы можете принять думать , что они равны. Вы можете установить, .1например.

Constantiner
источник
2

Обратите внимание, что если вы не занимаетесь математикой, нет ничего плохого в утверждении точных значений с плавающей запятой. Например:

public interface Foo {
    double getDefaultValue();
}

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

В этом случае вы хотите убедиться, что оно действительно MIN_VALUE, а не ноль или -MIN_VALUEили MIN_NORMALили какое-то другое очень маленькое значение. Ты можешь сказать

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

но вы получите предупреждение об устаревании. Чтобы избежать этого, assertEquals(Object, Object)вместо этого вы можете позвонить :

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

И, если вы действительно хотите выглядеть умно:

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

Или вы можете просто использовать утверждения Hamcrest в свободном стиле:

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

Если значение, которое вы проверяете, получается из-за какой-то математики, используйте epsilon.

Дэвид Моулз
источник
7
Если вы хотите проверить, точно ли он равен, установите epsilon равным 0.0 - вариант Object не требуется.
Мел Николсон
-2
Assert.assertTrue(Math.abs(actual-expected) == 0)
Пракаш
источник
При использовании чисел с плавающей запятой (например, с плавающей или двойной), это не будет работать надежно. Возможно, вы захотите посмотреть, как хранятся числа с плавающей запятой в Java и как с ними работают арифметические операции. (спойлер: ожидайте ошибок округления!)
Attila