Оцените, если два двойника равны на основе заданной точности, а не в пределах определенного фиксированного допуска
Я запускаю тесты 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?
Ответы
Ответ 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));