Подтвердить что ты не робот

Оцените, если два двойника равны на основе заданной точности, а не в пределах определенного фиксированного допуска

Я запускаю тесты 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?

4b9b3361

Ответ 1

Из msdn:

По умолчанию двойное значение содержит 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(expected, Is.EqualTo(actual).Within(tolerance));

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

Ответ 2

Это то, что я придумал для Руководство по плавающей запятой (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;
    }
}

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

Ответ 3

Как преобразовать элементы в строку и сравнить строки?

string test1 = String.Format("{0:0.0##}", expected);
string test2 = String.Format("{0:0.0##}", actual);
Assert.AreEqual(test1, test2);

Ответ 4

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

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
}

Ответ 5

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

1632.4587642911599 / (10^ceil(log10(1632.4587642911599))) = 0.16324587642911599

Ответ 6

Как насчет:

const double significantFigures = 10;
Assert.AreEqual(Actual / Expected, 1.0, 1.0 / Math.Pow(10, significantFigures));