Сравнение числа с плавающей точкой с нолем
C ++ FAQ lite “[29.17] Почему мое сравнение с плавающей запятой не работает?” рекомендует этот тест равенства:
#include /* for std::abs(double) */ inline bool isEqual(double x, double y) { const double epsilon = /* some small number such as 1e-5 */; return std::abs(x - y) <= epsilon * std::abs(x); // see Knuth section 4.2.2 pages 217-218 }
- Правильно ли это, что это означает, что единственные числа, равные нулю, равны
+0
и-0
? - Если использовать эту функцию также при тестировании на нуль или, скорее, на тест, такой как
|x| < epsilon
|x| < epsilon
?
Обновить
Как отметил Даниэль Даранас, функцию следует, вероятно, лучше назвать isNearlyEqual
(в этом я и забочусь).
- Могу ли я заставить gcc сказать мне, когда вычисление приводит к NaN или inf во время выполнения?
- Спецификатор ширины печати для поддержания точности значения с плавающей запятой
- Почему C не имеет неподписанных поплавков?
- Как красиво форматировать плавающие числа в String без ненужного десятичного числа 0?
- Каков самый быстрый способ конвертировать float в int на x86
Кто-то указал на эту ссылку , которую я хочу разделить более заметно.
- Почему Math.round (0.49999999999999994) возвращает 1?
- Какой тип данных MySQL следует использовать для широты / долготы с 8 десятичными знаками?
- Странное поведение при приведении float в int в C #
- Как я могу написать функцию питания самостоятельно?
- C ++ десятичные типы данных
- Преобразование float в строку
- Выдача результата для float в методе, возвращающем результат изменения float
- самое большое целое число, которое может быть сохранено в двойном
Вы верны своим наблюдениям.
Если x == 0.0
, то abs(x) * epsilon
равно нулю, и вы проверяете, будет ли abs(y) <= 0.0
.
Если y == 0.0
то вы тестируете abs(x) <= abs(x) * epsilon
что означает либо epsilon >= 1
(это не так), либо x == 0.0
.
Таким образом, либо is_equal(val, 0.0)
либо is_equal(0.0, val)
будет бессмысленным, и вы можете просто сказать val == 0.0
. Если вы хотите принять ровно +0.0
и -0.0
.
Рекомендация FAQ в этом случае имеет ограниченную полезность. Сравнение с плавающей запятой «один размер подходит всем». Вы должны думать о семантике ваших переменных, приемлемом диапазоне значений и величине ошибки, введенной вашими вычислениями. Даже в FAQ упоминается оговорка, говоря, что эта функция обычно не является проблемой «когда величины x и y значительно больше, чем epsilon, но ваш пробег может меняться».
Нет.
Равенство – это равенство.
Функция, которую вы написали, не будет проверять два двойника на равенство, поскольку их имя обещает. Он будет проверять только, если два двойника «достаточно близки» друг к другу.
Если вы действительно хотите проверить два двойника на равенство, используйте этот:
inline bool isEqual(double x, double y) { return x == y; }
Стандарты кодирования обычно рекомендуют не сравнивать два двойника для точного равенства. Но это другой вопрос. Если вы действительно хотите сравнить два удвоения для точного равенства, x == y
– код, который вы хотите.
10.00000000000000001 не равно 10.0, независимо от того, что они вам говорят.
Примером использования точного равенства является то, что конкретное значение double используется как синоним какого-либо особого состояния, например «ожидающая калибровка» или «отсутствие данных». Это возможно только в том случае, если фактические числовые значения после этого ожидающего вычисления являются лишь подмножеством возможных значений double. Наиболее типичным частным случаем является то, что это значение является неотрицательным, и вы используете -1.0 в качестве (точного) представления «ожидающего вычисления» или «отсутствия данных». Вы можете представить это с константой:
const double NO_DATA = -1.0; double myData = getSomeDataWhichIsAlwaysNonNegative(someParameters); if (myData != NO_DATA) { ... }
Вы можете использовать std::nextafter
с фиксированным factor
epsilon
значения, такого как:
bool isNearlyEqual(double a, double b) { int factor = /* a fixed factor of epsilon */; double min_a = a - (a - std::nextafter(a, std::numeric_limits::lowest())) * factor; double max_a = a + (std::nextafter(a, std::numeric_limits ::max()) - a) * factor; return min_a <= b && max_a >= b; }
2 + 2 = 5 (*)
( для некоторых значений с плавающей запятой 2 )
Эта проблема часто возникает, когда мы думаем о «плавающей точке» как способе повышения точности. Затем мы сталкиваемся с «плавающей» частью, что означает отсутствие гарантии того, какие числа могут быть представлены.
Поэтому, хотя мы можем легко представить «1.0, -1.0, 0.1, -0.1», поскольку мы получаем большие числа, мы начинаем видеть приближения – или мы должны, за исключением того, что мы часто скрываем их, обрезая числа для отображения.
В результате мы могли бы подумать, что компьютер хранит «0.003», но вместо этого он может хранить «0.0033333333334».
Что произойдет, если вы выполните «0.0003 – 0.0002»? Мы ожидаем .0001, но фактические значения, которые хранятся, могут быть больше похожими на «0.00033» – «0.00029», который дает «0,000004» или самое близкое представляемое значение , которое может быть 0, или оно может быть «0.000006».
С текущими математическими операциями с плавающей запятой не гарантируется, что (a / b) * b == a .
#include // defeat inline optimizations of 'a / b * b' to 'a' extern double bodge(int base, int divisor) { return static_cast(base) / static_cast (divisor); } int main() { int errors = 0; for (int b = 1; b < 100; ++b) { for (int d = 1; d < 100; ++d) { // b / d * d ... should == b double res = bodge(b, d) * static_cast (d); // but it doesn't always if (res != static_cast (b)) ++errors; } } printf("errors: %d\n", errors); }
ideone сообщает 599 экземпляров, где (b * d) / d! = b, используя только 10 000 комбинаций 1 <= b <= 100 и 1 <= d <= 100.
Решение, описанное в часто задаваемых вопросах, заключается в применении ограничения гранулярности – для проверки, if (a == b +/- epsilon)
.
Альтернативный подход заключается в том, чтобы полностью устранить проблему, используя точность фиксированной точки или используя желаемую детализацию в качестве базового блока для вашего хранилища. Например, если вы хотите, чтобы время хранилось с точностью до наносекунды, используйте наносекунды в качестве единицы хранения.
C ++ 11 ввел std :: ratio в качестве основы для конверсий с фиксированной точкой между разными единицами времени.
Если вас интересуют только +0.0
и -0.0
, вы можете использовать fpclassify
из
. Например:
if( FP_ZERO == fpclassify(x) ) do_something;
Как отметил @Exceptyon, эта функция является «относительной» к значениям, которые вы сравниваете. Мера Epsilon * abs(x)
будет масштабироваться на основе значения x, так что вы получите результат сравнения точно так же, как и epsilon
, независимо от диапазона значений в x или y.
Если вы сравниваете нуль ( y
) с другим действительно маленьким значением ( x
), скажем, 1e-8, abs(xy) = 1e-8
будет по-прежнему намного больше, чем epsilon *abs(x) = 1e-13
. Поэтому, если вы не имеете дело с чрезвычайно маленьким числом, которое не может быть представлено в двойном типе, эта функция должна выполнять задание и будет соответствовать нулю только против +0
и -0
.
Функция кажется совершенно верной для нулевого сравнения. Если вы планируете использовать его, я предлагаю вам использовать его везде, где есть поплавки, и не иметь особых случаев для таких вещей, как нуль, просто так, чтобы в коде существовала однородность.
ps: Это аккуратная функция. Спасибо, что указали на это.
Простое сравнение номеров FP имеет свою специфику, и ключевым моментом является понимание формата FP (см. https://en.wikipedia.org/wiki/IEEE_floating_point )
Когда числа FP вычисляются по-другому, одно через sin (), другое, хотя exp (), строгое равенство не будет работать, хотя математически числа могут быть равными. Точно так же не будет работать равенство с константой. Фактически, во многих ситуациях номера FP не должны сравниваться с использованием строгого равенства (==)
В таких случаях следует использовать константу DBL_EPSIPON, которая является минимальным значением , не меняя представления 1.0, добавляемого к числу более 1.0. Для чисел с плавающей запятой не существует всего 2.0 DBL_EPSIPON. Между тем, DBL_EPSILON имеет показатель -16, что означает, что все числа, скажем, с показателем -34, будут абсолютно равны по сравнению с DBL_EPSILON.
Также см. Пример , почему 10.0 == 10.0000000000000001
Сравнение чисел с плавающей запятой dwo зависит от характера этих чисел, мы должны рассчитать DBL_EPSILON для них, что было бы значимым для сравнения. Просто мы должны умножить DBL_EPSILON на одно из этих чисел. Какой из них? Максимально, конечно
bool close_enough(double a, double b){ if (fabs(a - b) <= DBL_EPSILON * std::fmax(fabs(a), fabs(b))) { return true; } return false; }
Все остальные способы дадут вам ошибки с неравенством, которые могут быть очень трудно поймать
обратите внимание, что код:
std::abs((x - y)/x) <= epsilon
вы требуете, чтобы «относительная ошибка» на var была <= epsilon, а не абсолютная разница