Должны ли мы сравнивать числа с плавающей запятой для равенства с ошибкой * relative *?
До сих пор я видел много сообщений, касающихся равенства чисел с плавающей запятой. Стандартный ответ на вопрос типа "как мы должны решить, если x и y равны?" это
abs(x - y) < epsilon
где epsilon - фиксированная малая константа. Это связано с тем, что "операнды" x и y часто являются результатом некоторых вычислений, в которых участвует ошибка округления, поэтому стандартный оператор равенства == - это не то, что мы имеем в виду, и то, что мы действительно должны задавать, - это то, являются ли x и y близкими, не равно.
Теперь я чувствую, что если x "почти равно" y, то также x * 10 ^ 20 должно быть "почти равным" y * 10 ^ 20 в том смысле, что относительная ошибка должна быть одинаковой ( но "относительный" к чему?). Но с этими большими числами вышеуказанное испытание потерпит неудачу, то есть это решение не "масштабируется".
Как вы справитесь с этой проблемой? Должны ли мы перемасштабировать цифры или перемасштабировать эпсилон? Как?
(Или моя интуиция не так?)
Вот вопрос , но мне не нравится его принятый ответ, поскольку вещь reinterpret_cast кажется мне немного сложной, я не понимаю, что продолжается. Попробуйте выполнить простой тест.
Ответы
Ответ 1
Все зависит от конкретной предметной области. Да, использование относительной ошибки будет более правильным в общем случае, но оно может быть значительно менее эффективным, поскольку оно включает дополнительное разделение с плавающей запятой. Если вы знаете приблизительную шкалу чисел в вашей проблеме, допустим абсолютную ошибку.
На этой странице описывается ряд методов сравнения поплавков. Он также затрагивает ряд важных вопросов, таких как проблемы с субнормальными явлениями, бесконечностями и NaN. Это замечательно, я настоятельно рекомендую прочитать его на всем протяжении.
Ответ 2
Как альтернативное решение, почему бы не просто округлить или усечь числа, а затем провести прямое сравнение? Установив количество значащих цифр заранее, вы можете быть уверены в точности в пределах этой границы.
Ответ 3
Проблема заключается в том, что при очень больших числах по сравнению с epsilon произойдет сбой.
Возможно, лучшим (но более медленным) решением будет использование деления, например:
div(max(a, b), min(a, b)) < eps + 1
Теперь "ошибка" будет относительной.
Ответ 4
Использование относительной ошибки по крайней мере не так плохо, как использование абсолютных ошибок, но у нее есть тонкие проблемы для значений около нуля из-за проблем округления. Далеко не идеальный, но довольно прочный алгоритм сочетает абсолютные и относительные погрешности:
boolean approxEqual(float a, float b, float absEps, float relEps) {
// Absolute error check needed when comparing numbers near zero.
float diff = abs(a - b);
if (diff <= absEps) {
return true;
}
// Symmetric relative error check without division.
return (diff <= relEps * max(abs(a), abs(b)));
}
Я адаптировал этот код от Брюса Доусона отличную статью Сравнивая числа с плавающей запятой, издание 2012 года, требуется прочитать для всех, кто делает сравнения с плавающей запятой - - удивительно сложная тема со многими подводными камнями.
Ответ 5
В большинстве случаев, когда код сравнивает значения, он делает это, чтобы ответить на какой-то вопрос. Например:
-
Если я знаю, какая функция возвращалась при задании значения X, могу ли я предположить, что она вернет то же самое, если задано Y?
-
Если у меня есть метод вычисления медленной, но точной функции, я готов принять некоторую неточность в обмен на скорость, и я хочу проверить кандидатскую функцию, которая, по-видимому, соответствует счету, - это выходы от этой функции достаточно близки к известной, точной, которая считается "правильной".
Чтобы ответить на первый вопрос, код должен в идеале поразмерно сравнивать его значение, но если язык не поддерживает новых операторов, добавленных в IEEE-754 в 2009 году, которые могут быть менее эффективными, чем идеальные. Чтобы ответить на второй вопрос, нужно определить, какая степень точности требуется и проверить на это.
Я не думаю, что существует много преимуществ в универсальном методе, который рассматривает как близкие близкие вещи, поскольку разные приложения будут иметь разные требования как к абсолютной, так и относительной толерантности, исходя из того, какие точные вопросы, ответ.