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

   ⁄ 

 Обновлено 5 Мар 2017

  ⁄   

Есть 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. Они не равны.

Иногда сравнение чисел типа с плавающей точкой бывает неизбежным. В таком случае операторы >, >=, < и <= используются как обычно, результаты будут правильными (ожидаемыми). А вот если два операнда очень близки по значениям, то результат уже может быть другой. Но, с операторами выше, последствия неправильного результата будут незначительны. А вот с оператором равенства дела обстоят хуже, так как даже при самой маленькой неточности результат сразу меняется на противоположный ожидаемому. Не рекомендуется использовать операторы == или != с числами типа floating point. Вместо них лучше использовать функцию, которая вычисляет, насколько близки два числа. Если они «достаточно близки», то мы считаем их равными. Значение, используемое для представления термина «достаточно близки», называется эпсилон. Оно, обычно, небольшое (например, 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 (10 оценок, среднее: 4,60 из 5)
Загрузка...
Поделиться в:
Подписаться на обновления:

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

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