Оцените, равны ли два двойных числа на основе заданной точности, а не в пределах определенного фиксированного допуска - PullRequest
29 голосов
/ 25 января 2011

Я запускаю тесты NUnit, чтобы оценить некоторые известные тестовые данные и вычисленные результаты.Числа представляют собой двойные числа с плавающей запятой, поэтому я не ожидаю, что они будут точно равными, но я не уверен, как обращаться с ними как с равными для заданной точности.

В NUnit мы можем сравнить с фиксированным допуском:

double expected = 0.389842845321551d;
double actual   = 0.38984284532155145d; // really comes from a data import
Expect(actual, EqualTo(expected).Within(0.000000000000001));

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

В частности, этот тест не пройден:

double expected = 1.95346834136148d;
double actual   = 1.9534683413614817d; // really comes from a data import
Expect(actual, EqualTo(expected).Within(0.000000000000001));

и, конечно, большие числа терпят неудачу с допуском.

double expected = 1632.4587642911599d;
double actual   = 1632.4587642911633d; // really comes from a data import
Expect(actual, EqualTo(expected).Within(0.000000000000001));

Какой правильный способ вычисления двух чисел с плавающей запятой равен с заданной точностью?Есть ли встроенный способ сделать это в NUnit?

Ответы [ 7 ]

16 голосов
/ 25 января 2011

Из msdn:

По умолчанию значение Double содержит 15 десятичных знаков точности, хотя внутренне поддерживается максимум 17 цифр.

Предположим, что 15, затем.

Итак, мы могли бы сказать, что мы хотим, чтобы допуск был в той же степени.

Сколько точных цифр у нас после десятичной запятой?Нам нужно знать расстояние от самой значащей цифры до десятичной точки, верно?Величина.Мы можем получить это с помощью Log10.

Затем нам нужно разделить точность 1 на 10 ^, чтобы получить значение вокруг точности, которую мы хотим.

Теперь вам нужно будет сделать больше тестов.случаев, чем у меня, но это, кажется, работает:

  double expected = 1632.4587642911599d;
  double actual = 1632.4587642911633d; // really comes from a data import

  // Log10(100) = 2, so to get the manitude we add 1.
  int magnitude = 1 + (expected == 0.0 ? -1 : Convert.ToInt32(Math.Floor(Math.Log10(expected))));
  int precision = 15 - magnitude ;

  double tolerance = 1.0 / Math.Pow(10, precision);

  Assert.That(actual, Is.EqualTo(expected).Within(tolerance));

Уже поздно - здесь может быть подвох.Я проверил это по вашим трем наборам тестовых данных, и каждый прошел.Изменение pricision на 16 - magnitude вызвало сбой теста.Установка его на 14 - magnitude, очевидно, заставила его пройти, так как допуск был больше.

8 голосов
/ 25 января 2011

Это то, что я придумал для Руководство с плавающей точкой (Java-код, но должен легко переводиться и поставляется с набором тестов, который вам действительно нужен):

public static boolean nearlyEqual(float a, float b, float epsilon)
{
    final float absA = Math.abs(a);
    final float absB = Math.abs(b);
    final float diff = Math.abs(a - b);

    if (a * b == 0) { // a or b or both are zero
        // relative error is not meaningful here
        return diff < (epsilon * epsilon);
    } else { // use relative error
        return diff / (absA + absB) < epsilon;
    }
}

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

5 голосов
/ 25 января 2011

Как насчет преобразования каждого элемента в строку и сравнения строк?

string test1 = String.Format("{0:0.0##}", expected);
string test2 = String.Format("{0:0.0##}", actual);
Assert.AreEqual(test1, test2);
3 голосов
/ 25 января 2011

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

double expected = 1632.4587642911599d;
double actual   = 1632.4587642911633d;
//for a precision of 4
long lActual = (long) 10000 * actual;
long lExpected = (long) 10000 * expected;

if(lActual == lExpected) {  // Do comparison
   // Perform desired actions
}
0 голосов
/ 28 марта 2018

Разница между этими двумя значениями должна быть меньше любого значения, деленного на точность.

Assert.Less(Math.Abs(firstValue - secondValue), firstValue / Math.Pow(10, precision));
0 голосов
/ 25 января 2011

Как насчет:

const double significantFigures = 10;
Assert.AreEqual(Actual / Expected, 1.0, 1.0 / Math.Pow(10, significantFigures));
0 голосов
/ 25 января 2011

Это быстрая идея, но как насчет сдвига их вниз, пока они не станут ниже нуля? Должно быть что-то вроде num/(10^ceil(log10(num))). , , не уверен в том, насколько хорошо это будет работать, но это идея.

1632.4587642911599 / (10^ceil(log10(1632.4587642911599))) = 0.16324587642911599
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...