Урок №42. Операторы сравнения

  Юрий  | 

    | 

  Обновл. 13 Апр 2019  | 

 13101

 ǀ   9 

Есть 6 операторов сравнения:

Оператор Символ Пример Операция
Больше > x > y true, если x больше y, в противном случае — false
Меньше < x < y true, если x меньше y, в противном случае — false
Больше или равно >= x >= y true, если x больше/равно y, в противном случае — false
Меньше или равно <= x <= y true, если x меньше/равно y, в противном случае — false
Равно == x == y true, если x равно y, в противном случае — false
Не равно != x != y true, если x не равно y, в противном случае — false

Вы уже могли их видеть в коде. Они довольно простые. Каждый из этих операторов вычисляется в логическое значение true (1) или false (0).

Вот несколько примеров использования этих операторов на практике:

Результат выполнения программы выше:

Enter an integer: 4
Enter another integer: 5
4 does not equal 5
4 is less than 5
4 is less than or equal to 5

Всё просто!

Сравнение чисел типа с плавающей точкой

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

Вот так раз:

d1 > d2

В программе выше, d1 = 0.0100000000000005116, а d2 = 0.0099999999999997868. Оба этих числа очень близки к 0.1, но d1 больше d2. Они не равны.

Иногда сравнение чисел типа с плавающей точкой бывает неизбежным. В таком случае следует использовать операторы >, <, >= и <= только если значения не очень близки. А вот если два операнда очень близки по значениям, то результат уже может быть неожиданный. В примере выше последствия неправильного результата незначительны, а вот с оператором равенства дела обстоят хуже, так как даже при самой маленькой неточности результат сразу меняется на противоположный ожидаемому. Не рекомендуется использовать операторы == или != с числами типа с плавающей точкой. Вместо них лучше использовать функцию, которая вычисляет, насколько близки эти два числа. Если они «достаточно близки», то мы считаем их равными. Значение, используемое для представления термина «достаточно близки», называется эпсилон. Оно, обычно, небольшое (например, 0.0000001).

Очень часто начинающие разработчики пытаются писать свои собственные функции определения равенства чисел:

fabs() — это функция из заголовочного файла cmath, которая возвращает абсолютное значение параметра. fabs(а − b) возвращает положительное число как разницу между а и b. Функция сравнивает разницу и эпсилон, вычисляя таким образом близость чисел. Если а и b достаточно близки, то функция возвращает true.

Хоть это и рабочий вариант, но он не идеален. Эпсилон 0.00001 подходит для чисел около 1.0, но будет слишком большим для чисел типа 0.0000001 и слишком малым для чисел типа 10 000. Это означает, что каждый раз, при вызове функции, нам нужно будет выбирать наиболее соответствующий входным данным функции эпсилон.

Дональд Кнут, известный учёный, предложил следующий способ в своей книге «Искусство программирования, том 2: Получисленные алгоритмы» (1968):

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

Рассмотрим детальнее, как работает функция approximatelyEqual(). Слева от оператора <= абсолютное значение (а − b) сообщает нам разницу между а и b (положительное число). Справа от <= нам нужно вычислить эпсилон, т. е. наибольшее значение «близости чисел», которое мы готовы принять. Для этого алгоритм выбирает большее из чисел а и b (как приблизительный показатель общей величины чисел), а затем умножает его на эпсилон. В этой функции эпсилон представляет собой процентное соотношение. Например, если термин «достаточно близко» означает, что а и b находятся в пределах 1% разницы (больше или меньше), то мы вводим эпсилон 1% (1% = 1/100 = 0.01). Его значение можно легко регулировать, в зависимости от обстоятельств (например, 0.01% = эпсилон 0.0001). Чтобы сделать неравенство (! =) вместо равенства — просто вызовите эту функцию, используя логический оператор НЕ (!), чтобы перевернуть результат:

Но и функция approximatelyEqual() также не идеальна, особенно когда дело доходит до чисел, близких к нулю:

Возможно, вы удивитесь, но результат:

1
0

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

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

Здесь мы добавили новый параметр — absEpsilon. Сначала мы сравниваем а и b с absEpsilon, который должен быть установлен как очень маленькое число (например, 1e-12). Таким образом, мы решаем случаи, когда a и b — нулевые значения или близки к нулю. Если это не так, то мы возвращаемся к алгоритму Кнута.

Протестируем:

Результат:

1
0
1

С удачно подобранным absEpsilon, функция approximatelyEqualAbsRel() обрабатывает нулевые значения правильно.

Сравнение чисел типа с плавающей точкой — сложная тема, и нет одного идеального алгоритма, который подойдёт в любой ситуации. Однако, для большинства случаев, с которыми вы будете сталкиваться, функции approximatelyEqualAbsRel() должно быть достаточно.


Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (100 оценок, среднее: 4,69 из 5)
Загрузка...

Комментариев: 9

  1. Аватар Вячеслав:

    интересно для написания торгового робота на криптобирже нужно применять функцию approximatelyEqualAbsRel() или нет?

    1. Юрий Юрий:

      Вы пишете ботов на С++ для криптобирж?

      1. Аватар Вячеслав:

        хочу попробовать

  2. Аватар Дед Максим:

    Первый урок, который я вообще не понял :). Видимо, из-за того, что не выспался. Код вообще не понятен. Пытаюсь — не выходит(

    1. Аватар Константин:

      Алло, Дед Максим! Ты когда пишешь рукой на листочек строку текста и приближаешься к правому краю и видишь, что последнее слово (если будешь продолжать таким же почерком) не помещается в строку, что делаешь? Правильно. Прижимистей буквы друг к другу тулишь. Это аналоговое представление значений. Цифровое же (то, которое в ЭВМ) — это когда все знаки и расстояния между ними строго одинаковы. И теперь представь себе, что точность — это ширина листа (если листок в клеточку, вообще, идеальная аналогия цифрового представления значений!) И вот тебе надо сравнить заряд электрона и заряд бозона. Что надо сделать? Правильно! Взять листочки по-ширше, т е. установить по-больше точность, иначе не влезающие цифры пропадут и вместо сравниваемых значений вообще какая-то дурь осядет. Но это ещё пол-беды! Подоплёка машинных "мансов" в том, что ЭВМ втихаря дописывает в клеточки левые цифры для заполнения пустующих после значащих цифр клеточек. Ну естественно результаты сравнения 100 — 99.99 и 10 — 9.99 с такими мансами будут не корректными! Да, дык о чём это я? А, вот пример: Требуется сравнить две трёхлитровых банки с жидкостью (молоко, самогон — по вкусу:-). Задаёмся граничным условием — если разница залитых объёмов не превышает одну пипетку (эпсилон) принимаем объёмы как равные. Пипетка — это абсолютный эпсилон, а объём пипетки/объём банки — это относительный эпсилон. А если объёмы сопоставимы с пипеткой (близки нулю)? Тогда Гулливер ловит лилипута, аннексирует у него пипетку (absEpsilon) и если разница меньше этого absEpsilon, то значения объёмов за "ноль" сойдут — не похмелишься (не наешься)!

  3. Аватар Oleksiy:

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

    1. Юрий Юрий:

      Ну это в реальной жизни 🙂 Та и в реальной жизни бывают исключения.

  4. Аватар Георгий:

    Кажется у меня отключился мозг после строчки: "Очень часто начинающие разработчики пытаются писать свои собственные функции определения равенства чисел:"

    1. Юрий Юрий:

      Почему? 🙂

Добавить комментарий для Юрий Отменить ответ

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