Как сравнивать double в c
Перейти к содержимому

Как сравнивать double в c

  • автор:

Как сравнивать double в c

Здравствуйте, gandjustas, Вы писали:

G>Нет. Вообще прямое сравнение чисел с плавающией запятой, вида (a==b) может дать неправильный результат
UN>Не 0.0000001, а EPS — это значение определено в CRT =)

Так. Тут начался беспредел с ужасами про сравнение плавающей точки на равенство. Требуется ликбез. Сравнивать плавающие числа (для педантов — значения переменных типа float или double) вполне можно и даже нужно. Но надо понимать сущность этой плавающей точки. А сущность заключается в том, что числа с фиксированной точкой (целые — это частный случай чисел с фиксированной точкой) имеют абсолютное значение погрешности, в отличие от чисел с плавающей точкой, где значение погрешности находится в прямой пропорциональности от модуля числа. Во всяком случае:

double a=3.1415; double b=a; if(a == b)

Вот если этот if не сработает, то это означает, что компьютер сломался, а процессор издох.

Другое дело, когда числа вычислены разными способами — одно через синус, а другое — через экспоненту. Здесь действительно проверка на равенство скорее всего не сработает. Так же, как и не сработает сравнение с константой. Но это же относится и к целым числам, если скажем, мы нормализуем значения от 0. 1 к 0. 1000000000. То есть, не имеет значения, плавающие это числа или целые. В определенных ситуациях сравнивать их на строгое равенство нельзя. В этих ситуациях надо использовать некую Epsilon. И вот здесь-то и вылезает наружу вся безграмотность. Что такое DBL_EPSILON? — а это вот что. Это минимальное значение, которое при прибавлении его к единице, меняет значение этой единицы. Понимаете? — к единице! Строгой единице, числу 1.0 и ни к какому другому. Поэтому сравнивать числа с плавающей точкой на +/- DBL_EPSILON совершенно бессмысленно. Это сравнение выдает всю глубину невежества и неспособности думать мозгом. Факты таковы — плавающие числа больше 2.0 эту DBL_EPSILON просто не ощущают. Им что прибавляй ее, что что нет — ничего не меняет. Для этих чисел DBL_EPSILON является строгим нулем и просто не существует. В то же время, DBL_EPSILON имеет значение порядка 1e-16. Что это значит? А это значит, что числа в диапазоне Планковских масштабов (типа 1e-34) с точки зрения этой DBL_EPSILON будут все равны. То есть, эта 1e-16 становится слоном в посудной лавке. А ведь постоянная Планка ничуть не хуже скорости света — для этого собственно и были придуманы числа с плавающей точкой, чтобы отображать большие диапазоны значений с неким фиксированным количеством знаков.

Так для чего же все-таки нужна эта самая DBL_EPSILON (ну или FLT_EPSILON)? Нужна-ли? — нужна! Есть ситуации, когда действительно надо сравнивать числа в неком допустимом интервале. В каком? — А вот это как раз и зависит от абсолютного значения чисел и сущности вычислений. Короче говоря, надо эту Epsilon умножить на значение числа. А поскольку у нас два числа, то все усложняется — какое из них брать. То есть, корректное сравнение выглядит так:

if (fabs(a-b) относительной точностью DBL_EPSILON >

Дорого? Да, дорого, а все остальное неправильно, такие дела. Но и это тоже неправильно! Дело в том, что этот DBL_EPSILON определяет разницу в 1 (один!) значащий бит экспоненты в приложении к числу 1.0. На практике такой разницы не встречается — числа либо строго равны, либо могут различаться больше чем на один значащий бит. Поэтому надо брать что-то типа 16*DBL_EPSILON, чтобы игнрорировать разницу в 4 младших бита (или примерно полторы последние значащие десятичные цифры из примерно 16 имеющихся).

Конечно же, есть случаи, когда диапазон чисел более-менее известен и предсказуем. Скажем, 0. 1000. В этом случае, для сравнения на приблизительное равенство можно взять константу, типа 1000*16*DBL_EPSILON. Но надо иметь в виду, что такое сравнение фактически превращает всю идею плавающей точки в фиксированную точку (догадайтесь, почему).

Я вообще поражен уровню невежества — даже в весьма грамотной библиотеке GPC by Alan Murta используется тупое сравнение с константной Epsilon. На диапазонах экранных координат это все равно, что сравнение на строгое равенство, а на 1e-20 алгоритм вообще перестает работать.

И пожалуйста, имейте смелость высмеять от меня в лицо того университетского преподавателя, который скажет вам, что сравнивать плавающие числа на равенство нельзя. Такое высказывание можно простить в школе, на первом уроке информатики, но никак не в универе.

Вроде бы все сказал. Дополняйте.

Безопасно ли сравнение == для типа double?

Что такое «безопасность» в вашем понимании? Да, безопасно сравнивать — компьютер не взорвется, и с вашей карты никто денег не снимет.

17 фев 2017 в 11:13

5 ответов 5

Сортировка: Сброс на вариант по умолчанию

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

#include #include bool is_equal(double x, double y) < return std::fabs(x - y) < std::numeric_limits::epsilon(); > 

Отслеживать
user177221
ответ дан 8 июл 2015 в 15:26
Denis Zaikin Denis Zaikin
441 4 4 серебряных знака 6 6 бронзовых знаков

Сравнение на строгое равенство почти никогда не безопасно. Вместо него следует использовать fabs(x-y) < EPS , где EPS - это некая константа, например, 1e-7 .

Единственный случай, когда можно позволить себе строгое сравнение — это если есть некая константа, которую ты мог сам присвоить (явно, без вычислений), и с которой сравниваешь для того, чтобы узнать, было ли переприсваивание. И даже в этом месте надо быть осторожным, потому что известны случаи, когда благодаря оптимизации у сравниваемых величин оказывается разный тип, что приводит к разной точности и неравенству.

Ещё все вычисления с целыми числами до 2 53 точные. Это используется языками с единственным числовым типом (например js). Однако, если ты уверен, что значение целое, то зачем вообще использовать double ? Лучше взять 64-битный целый тип.

Как сравнить тип данных double/float в условии?

Пишу:
if($arr[‘P’] == 0.5)
не работает. не попадает в выборку данный массив. как правильно сравнивать подобный тип данных и с чем может быть связана проблема?

UPD:
Как оказалось, проблема не в этом, а в невозможности сравнивать сразу 3 условия, если сравниваю любые другие два, то все работает. Не понимаю, в чем дело.

  • Вопрос задан более трёх лет назад
  • 5853 просмотра

Комментировать
Решения вопроса 1
gelirgwenn @gelirgwenn

Вы некорректно сравниваете числа типа float. Все числа хранятся в двоичном представлении на компьютере, так уж заведено, выбрана такая система счисления, потому что информацию в виде нулей и единиц легче обрабатывать и легче делать чипы и т.п.
Так вот некоторые числа не имеют точного представления в двоичной системе. Например, 0,2 имеет вид примерно такой 0,2000000033. Соответственно, при сравнении в лоб, вы не сможете гарантировать корректный результат. Вы можете, к примеру, пробовать сравнивать сравнивать разницу с допустимой абсолютной погрешностью:

if (abs($x – $y) < 0.0001) < // Do something. >

Недостаток данного метода описан здесь, а также другие способы сравнения.
Я бы вам рекомендовал перевести float в целое число или строку. В первом случае вы будете сравнивать два целых числа и у вас не будет проблем, во втором — с помощью функции number_format и заданной точности вы преобразуете число в строку, а далее можете сравнивать строки, содержащие числа, с помощью функции bccomp или аналога:

$x = 0.200000000646456546; $y = 0.200000000676790909; $results = ($x == $y); var_dump($results); // bool(false) $results = bccomp(number_format($x, 5), number_format($y, 5)); var_dump($results); // int(0) - are equal

Быстрое сравнение double

Вчера здесь вышла статья о быстром парсинге double, я зашёл во блог к её автору, и нашёл там ещё один интересный трюк. При сравнении чисел с плавающей точкой особое внимание приходится уделять NaN (восемь лет назад я писал про них подробнее); но если сравниваемые числа заведомо не NaN, то сравнить их можно быстрее, чем это делает процессор!

Положительные double сравнивать очень просто: нормализация гарантирует нам, что из чисел с разной экспонентой больше то, чья экспонента больше, а из чисел с равной экспонентой больше то, чья мантисса больше. Стандарт IEEE 754 заботливо поместил экспоненту в старшие биты, так что положительные double можно сравнивать просто как int64_t.

С отрицательными числами немного сложнее: они хранятся в прямом коде, тогда как int64_t — в дополнительном. Это значит, что для использования целочисленного сравнения младшие 63 бита double необходимо инвертировать (при этом получится -0. < +0., что не соответствует стандарту, но на практике не представляет проблемы). Явная проверка старшего бита и условный переход уничтожили бы всю выгоду от перехода к целочисленному сравнению; но есть способ проще!

inline int64_t to_int64(double x) < int64_t a = *(int64_t*)&x; uint64_t mask = (uint64_t)(a >> 63) >> 1; return a ^ mask; > inline bool is_smaller(double x1, double x2)

a>>63 заполняет все 64 бита копиями знакового бита, и затем >>1 обнуляет старший бит.

Во блоге у Daniel Lemire несколько другой код (той же вычислительной сложности), но мой вариант сохраняет то полезное свойство, что to_int64(0.) == 0

  • Высокая производительность
  • Ненормальное программирование
  • C

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *