Стоит ли сравнивать числа с плавающей запятой на равенство с * относительной * ошибкой? - PullRequest
15 голосов
/ 30 ноября 2008

До сих пор я видел много постов, посвященных равенству чисел с плавающей запятой. Стандартный ответ на вопрос типа «как мы должны решить, равны ли x и y?» есть

abs(x - y) < epsilon

где epsilon является фиксированной , малой константой. Это связано с тем, что «операнды» x и y часто являются результатами некоторых вычислений, в которых возникает ошибка округления, поэтому стандартный оператор равенства == - это не то, что мы имеем в виду, и мы должны спросить, действительно ли x и y закрыть , не равно.

Теперь я чувствую, что если x "почти равен" y, то и x * 10 ^ 20 должен быть "почти равен" y * 10 ^ 20 в том смысле, что относительно ошибка должна быть такой же (но «относительно» чего?). Но с этими большими числами вышеуказанный тест не пройден, т. Е. Это решение не «масштабируется».

Как бы вы справились с этой проблемой? Должны ли мы изменить масштаб чисел или изменить эпсилон? Как? (Или моя интуиция не так?)

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

Ответы [ 5 ]

19 голосов
/ 30 ноября 2008

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

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

4 голосов
/ 30 ноября 2008

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

1 голос
/ 30 ноября 2008

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

Возможно, лучшим (но более медленным) решением было бы использование деления, например:

div(max(a, b), min(a, b)) < eps + 1

Теперь «ошибка» будет относительной.

0 голосов
/ 28 февраля 2015

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

  1. Если я знаю, что функция вернула, когда ей дано значение X, могу ли я предположить, что она вернет то же самое, если дано Y?

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

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

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

0 голосов
/ 26 февраля 2015

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

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

...