Должны ли мы сравнивать числа с плавающей запятой для равенства с ошибкой * 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 году, которые могут быть менее эффективными, чем идеальные. Чтобы ответить на второй вопрос, нужно определить, какая степень точности требуется и проверить на это.

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