тестирование java juint, какую дельту выбрать, чтобы избежать ошибки с плавающей точкой - PullRequest
0 голосов
/ 26 мая 2018

Я использую jUnit для тестирования своего Java-приложения (я новичок в Java).

public class MyClass {
    public static void main(String args[]) {

        double A = 50000.0;
        double B = 1.1;
         System.out.println("Result" + A * B);
    }
}

«Нормальный» ответ (с математической точки зрения): 55000

Однако возвращается Java:

Result 55000.00000000001

Таким образом, мой тест не пройден, поскольку assertconsiditonне соблюдается

Мой тест jUnit выглядит следующим образом:

        Assert.assertEquals("55000", <javaResult>, 0.0);

Я считаю, что проблема связана с последним параметром, называемым дельта.Потому что когда я пытаюсь изменить дельту случайно.Следующее не дает сбоя (т. Е. Тест пройден, как и ожидалось)

        Assert.assertEquals("55000", <javaResult>, 0.01);

Итак, мой вопрос: если я должен выполнять исключительно умножения, какую дельту выбрать?(Я не чувствую себя комфортно, выбрав случайно 0,01 только потому, что это работает ...)

1 Ответ

0 голосов
/ 27 мая 2018

В нормальном диапазоне (величины в [2 -1022 , 2 1024 )) Java double имеет 53-битное значение, поэтому размер шага между соседнимипредставимые значения составляют не более одной части в 2 52 .Всякий раз, когда число конвертируется в формат double с использованием округления до ближайшего, результат, если он находится в нормальном диапазоне, находится на расстоянии не более половины шага от исходного числа.

Таким образом, если a - это значение десятичной цифры, которое преобразуется в double значение a, затем a = a • (1 + e ), где | e |≤ 2 −53 .

Рассмотрим сравнение математического произведения двух чисел xy с результатом преобразования двух чисел в double x иy и умножение их для получения результата double.Мы можем записать точное произведение xy в виде десятичной цифры, но, передав его в assertEquals, оно будет преобразовано в double, поэтому мы фактически передаем xy • (1 + e 0 ) для некоторых e 0 , как описано выше.

Аналогично, x преобразуется в некоторые x, равные x • (1 + e 1 ), а y преобразуется в некоторыеy равно y • (1 + e 2 ).Затем мы умножаем их на форму ( x • (1 + e 1 )) • ( y • (1 + e 2 )) • (1 + e 3 ).

Наконец, мы сравним xy • (1 + e 0 ) до ( x • (1 + e 1 )) • ( у • (1 + е 2 )) • (1 + е 3 ).Насколько они могут отличаться?

Последний - xy • (1 + e 1 ) • (1 + e 2 ) • (1 + e 3 ), поэтому разница составляет xy • ((1 + e )1 ) • (1 + е * ** одна тысячи сто двадцать восемь тысяча сто двадцать девять * 2 ) • (1 + * * е тысяча сто тридцать один * ** 1133 один тысяча сто тридцать два * 3 * * одна тысяча сто тридцать четыре) - (1 + e 0 )) = xy • ( e 1 + e 2 + * * е тысяча сто сорок-девять * +1151 * 3 * + * тысяча сто пятьдесят-два е * +1155 * 1 * * 1 157 е * * 2 тысяча сто пятьдесят-девять + е * * 2 тысячи сто шестьдесят три * +1164 * е * 1 167 * 3 * +1168 * + е * 1 171 * 1 е * 1 174 *3 + e 1 e 2 e 3 - е 0 ).Мы можем легко увидеть, что термин ошибки имеет наибольшую величину, когда e 0 равно -2 −53 , а другие ошибки равны + 2 −53 .Тогда это 4 • 2 −53 + 3 • 2 −106 + 2 −159 .Независимо от того, является ли x или y положительным или отрицательным, наибольшая величина этой погрешности остается неизменной, поэтому мы можем охарактеризовать эту границу разности как| • (4 • 2 −53 + 3 • 2 −106 + 2 −159 ).

Мы не можем вычислить это точно, используяdouble, по трем причинам:

  • xy не может быть точно представлен.
  • 4 • 2 −53 + 3 •2 −106 + 2 −159 не представляется.
  • Когда эти два значения умножаются с использованием double, возможна другая ошибка округления.

Чтобы справиться с первой проблемой, предположим, что у нас есть абсолютное значение желаемого продукта, xy , в виде десятичной цифры N.Тогда мы можем заменить | xy |в выражении с Math.nextAfter(N, Double.POSITIVE_INFINITY).Это добавляет небольшое количество (малейшее возможное), чтобы компенсировать тот факт, что N может округляться при преобразовании в double.(Мы также можем подготовить N, преобразовав его в double с округлением в сторону ∞ вместо округления до ближайшего.)

Чтобы справиться со второй проблемой, мы можем преобразовать 4 • 2 -53 + 3 • 2 −106 + 2 −159 до double с округлением в направлении ∞.Результат равен 4 • 2 −53 + 3 • 2 −106 или 2 −51 + 2 −103 .Начиная с JDK 5 мы можем записать это как 0x1.0000000000001p-51.

Третья проблема может привести к ошибке округления не более 2 -53 относительно результата.Однако при преобразовании члена ошибки в double мы округлились более чем на это (сумма, на которую 2 −103 превышает 3 • 2 −106 + 2 −159 меньше, чем 2 -53 , умноженное на ошибку), поэтому, даже если вычисленный результат округляется в 2 −53 (относительно), он все равновыше желаемого математического результата.

Таким образом, Assert.assertEquals("<exactResult>", <javaResult>, Math.nextAfter(N, Double.POSITIVE_INFINITY)*0x1.0000000000001p-51); сообщает об ошибке, только если хотя бы одно из следующих условий верно:

  • <javaResult> не является результатом вычисления вdouble произведение двух чисел, преобразованных из десятичных чисел, точное значение которых равно <exactResult>.
  • <exactResult> не находится в нормальном диапазоне double.
  • Расчетный допуск равенне в нормальном диапазоне double (что приводит к его округлению в большую сторону, чем ожидалось выше).(Обратите внимание, что второе условие подразумевает это третье условие, поэтому второе может быть опущено.)

Если <exactResult> находится в субнормальном диапазоне, для допуска может использоваться небольшое абсолютное значение,вместо относительного значения.Я опускаю обсуждение этого.

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

Это верхняя граница ошибки.Можно было бы еще более ужесточить его с помощью еще большего анализа, а в некоторых обстоятельствах можно ужесточить еще больше, например, если известно, что xy точно представимо, или если конкретные значения для x и y известны.

Я ожидаю, что nextAfter можно заменить увеличением коэффициента ошибок, но я не провел анализ, чтобы утверждать, чтоскажем, одного увеличения ULP достаточно.

...