Значення аргументу epsilon arcertEquals для подвійних значень


187

У мене є питання щодо джуніту assertEqualsдля перевірки подвійних значень. Читаючи документ API, я можу побачити:

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

Застарілий. Використовуйте замість assrtEquals (подвійний очікуваний, подвійний фактичний, подвійний епсилон)

Що означає epsilonзначення? (Епсілон - літера грецького алфавіту, правда?).

Може хтось пояснить мені, як ним користуватися?

Відповіді:


198

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


3
Отже, яке значення я повинен передати як epsilon?
смарагдовий

15
@ Emerald214 величина точності. Якщо ви хочете стверджувати, що подвійне значення 0D епсилон буде 0 (100% точність, без винятку). Якщо ви бажаєте похибки (скажімо, для градусів), ви можете встановити епсилон на 1, тобто 64,2 °, наприклад, 64,8 ° (оскільки абс (64,8-64,2) <1)
Пітер Де Бі,

3
У документації йдеться про "дельта - максимальна дельта між очікуваною та фактичною, для якої обидва числа все ще вважаються рівними". Тому я думаю, що це повинно бути <=не <.
Ендрю Чонг

Дивлячись на код, я бачу, що він викликає метод doubleIsDifferent(для порівняння подвійних значень) і він повертається Math.abs(d1 - d2) > delta. Отже, якщо різниця між d1 і d2 більша, ніж дельта, це означає, що значення різні і повернуть істинні. Він поверне помилковим, якщо значення вважати рівними. Цей метод викликається у assertEquals безпосередньо, і якщо він повернеться істинним, 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));
}

FYI майбутнього JUnit 5 також зробить дельту необов’язковою при дзвінках assertEquals()з двома парними. Реалізація (якщо ви зацікавлені) є:

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

57

Розрахунки з плаваючою точкою не точні - часто виникають помилки округлення та помилки через представлення. (Наприклад, 0,1 не може бути точно представлений у двійковій плаваючій точці.)

Через це безпосередньо порівняння двох значень з плаваючою точкою для рівності зазвичай не є хорошою ідеєю, оскільки вони можуть бути різними на невелику кількість, залежно від способу їх обчислення.

"Дельта", як її називають в JUnit javadocs , описує різницю, яку ви можете допустити у значеннях, щоб вони все ще вважалися рівними. Розмір цього значення повністю залежить від значень, які ви порівнюєте. При порівнянні парних пар я зазвичай використовую очікуване значення, поділене на 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наприклад.


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));

Якщо значення, яке ви перевіряєте , походить від математики, використовуйте епсилон.


7
Якщо ви хочете перевірити, чи точно дорівнює, встановіть епсилон 0,0 - варіант об'єкта не потрібно.
Мел Ніколсон

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

Якщо використовуються номери з плаваючою комою (наприклад, плаваючі або подвійні), це не працює надійно. Ви можете переглянути, як зберігаються номери з плаваючою комою в java і як над ними працюють арифметичні операції. (спойлер: очікуйте деяких помилок округлення!)
Аттіла
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.