какая запись операторов сравнения верна
Урок №42. Операторы сравнения
Обновл. 11 Сен 2021 |
В языке C++ есть 6 операторов сравнения:
| Оператор | Символ | Пример | Операция |
| Больше | > | x > y | true, если x больше y, в противном случае — false |
| Меньше | = | x >= y | true, если x больше/равно y, в противном случае — false |
| Меньше или равно | #include |
Результат выполнения программы:
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
Сравнение чисел типа с плавающей точкой
Сравнение значений типа с плавающей точкой с помощью любого из этих операторов — дело опасное. Почему? Из-за тех самых небольших ошибок округления, которые могут привести к неожиданным результатам. Например:
Очень часто начинающие разработчики пытаются писать свои собственные функции определения равенства чисел:
Функция isAlmostEqual() из примера, приведенного выше, сравнивает разницу (a − b) и эпсилон, вычисляя, таким образом, можно ли считать эти числа равными. Если разница между а и b очень мала, то функция возвращает true.
Хоть это и рабочий вариант, но он не идеален. Эпсилон 0.00001 подходит для чисел около 1.0, но будет слишком большим для чисел типа 0.0000001 и слишком малым для чисел типа 10000. Это означает, что каждый раз при вызове функции нам нужно будет выбирать наиболее соответствующий входным данным функции эпсилон.
Дональд Кнут, известный учёный, предложил следующий способ в своей книге «Искусство программирования, том 2: Получисленные алгоритмы» (1968):
Здесь, вместо использования эпсилона как абсолютного числа, мы используем его как умножитель, чтобы подстроиться под входные данные.
Но и функция approximatelyEqual() тоже не идеальна, особенно, когда дело доходит до чисел, близких к нулю:
Возможно, вы удивитесь, но результат:
Второй вызов не сработал так, как ожидалось. Математика просто ломается, когда дело доходит до нулей.
Но и этого можно избежать, используя как абсолютный эпсилон (то, что мы делали в первом способе), так и относительный (способ Кнута) вместе:
Сравнение чисел типа с плавающей точкой — сложная тема, и нет одного идеального алгоритма, который подойдет в любой ситуации. Однако для большинства случаев, с которыми вы будете сталкиваться, функции approximatelyEqualAbsRel() должно быть достаточно.
Поделиться в социальных сетях:
Урок №41. Условный тернарный оператор, оператор sizeof и Запятая
Комментариев: 21
Если в С++ такая проблема со сравнением дробных чисел, не будет ли логичнее создать отдельный класс? Чтобы каждый объект его состоял из трёх целых чисел (целая часть, дробная часть и количество цифр справа от запятой), а значит не возникало необходимости придумывать функции типа «приблизительно равно» и т.п.
Здравствуйте!
Как правильно сравнивать высоту ( в дес. дробях 0,00 м) саму с собой через одну секунду?
Задача поймать точку прохождения апогея (максимальной высоты).
Написали такое, можете что получше подсказать?
А почему нельзя взять взять за вычисляемый эпсилон среднее арифметическое абсолютных значений сравниваемых величин умноженное на эпсилон? Код вроде попроще будет.
Можно и так наверно, но мне кажется тут берется большее число, потому что всегда надо рассматривать худший случай
Если при сравнении чисел указать тип float вместо double, то результатом будет true, даже при обычном сравнении. Это специфика компилятора или есть еще что-то?
Я тоже заметил что float точный, думаю нужно просто запомнить что double и long double имеют такие костыли.
Почему так уверены? У float будет всё то же самое. Принцип хранения таких чисел ведь одинаковый, что флоат что дабл. А в данном случае у вас просто удачное совпадение. Попробуйте с другими числами и найдёте «неудачные».
Возможно, вы удивитесь, но результат:
Второй вызов не сработал так, как ожидалось. Математика просто ломается, когда дело доходит до нулей.
Почему?
Тяжеловата тема, но интересно.
Наибольшая сложность — не знаешь сразу куда применять.
Пожалуйста 🙂 Главное — не зацикливайтесь, если что — вернётесь позже к этому уроку.
интересно для написания торгового робота на криптобирже нужно применять функцию approximatelyEqualAbsRel() или нет?
Вы пишете ботов на С++ для криптобирж?
Первый урок, который я вообще не понял :). Видимо, из-за того, что не выспался. Код вообще не понятен. Пытаюсь — не выходит(
Алло, Дед Максим! Ты когда пишешь рукой на листочек строку текста и приближаешься к правому краю и видишь, что последнее слово (если будешь продолжать таким же почерком) не помещается в строку, что делаешь? Правильно. Прижимистей буквы друг к другу тулишь. Это аналоговое представление значений. Цифровое же (то, которое в ЭВМ) — это когда все знаки и расстояния между ними строго одинаковы. И теперь представь себе, что точность — это ширина листа (если листок в клеточку, вообще, идеальная аналогия цифрового представления значений!) И вот тебе надо сравнить заряд электрона и заряд бозона. Что надо сделать? Правильно! Взять листочки по-ширше, т е. установить по-больше точность, иначе не влезающие цифры пропадут и вместо сравниваемых значений вообще какая-то дурь осядет. Но это ещё пол-беды! Подоплёка машинных «мансов» в том, что ЭВМ втихаря дописывает в клеточки левые цифры для заполнения пустующих после значащих цифр клеточек. Ну естественно результаты сравнения 100 — 99.99 и 10 — 9.99 с такими мансами будут не корректными! Да, дык о чём это я? А, вот пример: Требуется сравнить две трёхлитровых банки с жидкостью (молоко, самогон — по вкусу:-). Задаёмся граничным условием — если разница залитых объёмов не превышает одну пипетку (эпсилон) принимаем объёмы как равные. Пипетка — это абсолютный эпсилон, а объём пипетки/объём банки — это относительный эпсилон. А если объёмы сопоставимы с пипеткой (близки нулю)? Тогда Гулливер ловит лилипута, аннексирует у него пипетку (absEpsilon) и если разница меньше этого absEpsilon, то значения объёмов за «ноль» сойдут — не похмелишься (не наешься)!
Радует то, что в реальной жизни чаще требуется сравнивать целые числа. А когда доходит до чисел с плавающей точкой, то там почти всегда не важно «>» или «>=».
Ну это в реальной жизни 🙂 Та и в реальной жизни бывают исключения.
Кажется у меня отключился мозг после строчки: «Очень часто начинающие разработчики пытаются писать свои собственные функции определения равенства чисел:»
Операторы сравнения
Используется для сравнения выражений.
Синтаксис
результат = выражение comparisonoperator expression12
результат = object1 Is object2
результат = строка Like pattern
Операторы сравнения состоят из следующих частей:
| Part | Описание |
|---|---|
| result | Обязательный элемент; любая числовая переменная. |
| выражение | Обязательный элемент; любое выражение. |
| comparisonoperator | Обязательный элемент; любой оператор сравнения. |
| object | Обязательный; любое имя объекта. |
| строка | Обязательный элемент; любое строковое выражение. |
| pattern | Обязательный элемент; любое строковое выражение или диапазон символов. |
Примечания
В следующей таблице содержится список операторов сравнения и условия, которые определяют, является ли результат True, False или Null.
| Оператор | True, если | False, если | Null, если | ||||
|---|---|---|---|---|---|---|---|
| (Меньше) | expression1 = expression2 | expression1 или expression2 = Null | |||||
| (Меньше или меньше) | expression1 expression2 | expression1 или expression2 = Null | |||||
| > (Больше) | expression1 > expression2 | expression1 >= (Больше или равно) | expression1 >= expression2 | expression1 = (Равно) | expression1 = expression2 | expression1 <> expression2 | expression1 или expression2 = Null |
| <> (Не равно) | expression1 <> expression2 | expression1 = expression2 | expression1 или expression2 = Null |
Операторы Is и Like обладают особыми возможностями сравнения, которые отличаются от возможностей операторов, приведенных в таблице.
При сравнении двух выражений, возможно, будет нелегко определить, сравниваются ли выражения как числа или как строки. В следующей таблице показано сравнение выражений или результат, когда выражение не является вариантом.
| Если | Then |
|---|---|
| Оба выражения представляют собой числовые типы данных (Byte, Boolean, Integer, Long, Single, Double, Date, Currency или Decimal) | Выполните числовое сравнение. |
| Оба выражения имеют тип String | Выполняется сравнение строк. |
| Одно выражение является числовым типом данных, а другое — типом Variant, который равен числу или может им быть | Выполните числовое сравнение. |
| Одно выражение является числовым типом данных, а другое — строкой Variant, которая не может быть преобразована в число | Возникает Type Mismatch ошибка. |
| Одно выражение — String, а другое — любое значение Variant за исключением Null | Выполните строковое сравнение. |
| Одно выражение — Empty, а другое является числовым типом данных | Выполните числовое сравнение, используя 0 в качестве выражения Empty. |
| Одно выражение — Empty, а другое — String | Выполните строковое сравнение, используя строку нулевой длины («») в качестве выражения Empty. |
Если оба выражения expression1 и expression2 являются выражениями типа Variant, порядок их сравнения определяется их базовым типом. В следующей таблице показано сравнение выражений или результат сравнения в зависимости от типа варианта.
| Если | Then |
|---|---|
| Оба выражения Variant являются числовыми | Выполните числовое сравнение. |
| Оба выражения Variant являются строками | Выполните строковое сравнение. |
| Одно выражение Variant числовое, а другое строка | Числовое выражение меньше, чем строковое выражение. |
| Одно выражение Variant — Empty, а другое — числовое | Выполните числовое сравнение, используя 0 в качестве выражения Empty. |
| Одно выражение Variant — Empty, а другое — строка | Выполните строковое сравнение, используя строку нулевой длины («») в качестве выражения Empty. |
| Оба выражения Variant — Empty | Выражения равны. |
Когда переменная Single сравнивается с переменной Double, значение Double округляется до точности типа Single. Если переменная Currency сравнивается с переменной Single или Double, значение Single или Double преобразуется в тип Currency.
Аналогично, когда переменная Decimal сравнивается с переменной Single или Double, значение Single или Double преобразуется в тип Decimal. Для типа Currency любое дробное значение менее чем 0,0001 может быть потеряно; для типа Decimal любое дробное значение менее чем 1E-28 может быть потеряно, или возможно возникновение ошибки переполнения. Потеря такого дробного значения может привести к тому, что два сравниваемых значения будут равны, когда на самом деле они не равны.
Пример
В этом примере показаны различные варианты использования операторов сравнения, которые применяются для сравнения выражений.
См. также
Поддержка и обратная связь
Есть вопросы или отзывы, касающиеся Office VBA или этой статьи? Руководство по другим способам получения поддержки и отправки отзывов см. в статье Поддержка Office VBA и обратная связь.
9. Java — Основные операторы языка
Java предоставляет богатый набор операторов для управления переменными. Все операторы Java можно разделить на следующие группы:
Содержание
Арифметические операторы
Арифметические операторы — используются в математических выражениях таким же образом, как они используются в алгебре. Предположим, целая переменная A равна 10, а переменная B равна 20. В следующей таблице перечислены арифметические операторы в Java:
Пример
Следующий простой пример показывает программно арифметические операторы. Скопируйте и вставьте следующий java-код в файл test.java, скомпилируйте и запустить эту программу:
Это произведет следующий результат:
Операторы сравнения
Есть следующие операторы сравнения, поддерживаемые на языке Java. Предположим, переменная A равна 10, а переменная B равна 20. В следующей таблице перечислены реляционные операторы или операторы сравнения в Java:
Пример
Следующий простой пример показывает, программно побитовые операторы в Java. Скопируйте и вставьте следующий java-код в файл test.java, скомпилируйте и запустить эту программу:
Будет получен следующий результат:
Логические операторы
Предположим, логическая переменная A имеет значение true, а переменная B хранит false. В следующей таблице перечислены логические операторы в Java:
| Оператор | Описание | Пример |
| && | Называется логический оператор «И». Если оба операнда являются не равны нулю, то условие становится истинным | (A && B) — значение false |
| || | Называется логический оператор «ИЛИ». Если любой из двух операндов не равен нулю, то условие становится истинным | (A || B) — значение true |
| ! | Называется логический оператор «НЕ». Использование меняет логическое состояние своего операнда. Если условие имеет значение true, то оператор логического «НЕ» будет делать false | !(A && B) — значение true |
Пример
Следующий простой пример показывает, программно логические операторы в Java. Скопируйте и вставьте следующий java-код в файл test.java, скомпилируйте и запустить эту программу:
Это произведет следующий результат:
Операторы присваивания
Существуют следующие операторы присваивания, поддерживаемые языком Java:
Пример
Следующий простой пример показывает, программно логические операторы в Java. Скопируйте и вставьте следующий java-код в файл test.java, скомпилируйте и запустить эту программу:
Будет получен следующий результат:
Прочие операторы
Есть несколько других операторов, поддерживаемых языком Java.
Тернарный оператор или условный оператор (?:)
Тернарный оператор — оператор, который состоит из трех операндов и используется для оценки выражений типа boolean. Тернарный оператор в Java также известен как условный оператор. Этот. Цель тернарного оператора или условного оператора заключается в том, чтобы решить, какое значение должно быть присвоено переменной. Оператор записывается в виде:
Пример
Ниже приведен пример:
Будет получен следующий результат:
Оператор instanceof
Оператор instanceof — проверяет, является ли объект определенного типа (типа класса или типа интерфейса) и используется только для переменных ссылочного объекта. Оператор instanceof записывается в виде:
Примеры
Если переменная ссылочного объекта в левой части оператора проходит проверку для класса/типа интерфейса на правой стороне, результатом будет значение true. Ниже приведен пример и описание оператора instanceof:
Будет получен следующий результат:
Этот оператор по-прежнему будет возвращать значение true, если сравниваемый объект является совместимым с типом на право назначения. Ниже приводится еще один пример:
Будет получен следующий результат:
Приоритет операторов в Java
Приоритет операторов определяет группирование терминов в выражении. Это влияет как вычисляется выражение. Некоторые операторы имеют более высокий приоритет, чем другие; например оператор умножения имеет более высокий приоритет, чем оператор сложения:
Например, x = 7 + 3 * 2. Здесь x присваивается значение 13, не 20, потому что оператор «*» имеет более высокий приоритет, чем «+», так что сначала перемножается «3 * 2», а затем добавляется «7».
В таблице операторы с наивысшим приоритетом размещаются в верхней части, и уровень приоритета снижается к нижней части таблицы. В выражении высокий приоритет операторов в Java будет оцениваться слева направо.
Операции сравнения в C++20
Встреча в Кёльне прошла, стандарт C++20 приведён к более или менее законченному виду (по крайней мере до появления особых примечаний), и я хотел бы рассказать об одном из грядущих нововведений. Речь пойдёт о механизме, который обычно называют operator (стандарт определяет его как «оператор трёхстороннего сравнения», но у него есть неформальное прозвище «космический корабль»), однако я считаю, что область его применения гораздо шире.
У нас не просто будет новый оператор — семантика сравнений претерпит существенные изменения на уровне самого языка.
Даже если ничего больше вы из этой статьи не вынесете, запомните эту таблицу:
Об этих возможностях мы поговорим коротко во вступлении и рассмотрим подробнее в следующих разделах.
Рассмотрим небольшой пример, в котором покажем, как выглядит код до и после применения нового функционала. Мы напишем тип строки, не учитывающий регистр, CIString, объекты которого могут сравниваться как друг с другом, так и с char const*.
В C++17 для нашей задачи потребуется написать 18 функций сравнения:
В C++20 можно обойтись всего лишь 4 функциями:
Я расскажу, что всё это значит, подробнее, но сначала давайте немного вернёмся в прошлое и вспомним, как работали сравнения до стандарта C++20.
Сравнения в стандартах с C++98 по C++17
С трёхсторонними сравнениями мы уже знакомы по функциям memcmp/strcmp в C и basic_string::compare() в C++. Все они возвращают значение типа int, которое представлено произвольным положительным числом, если первый аргумент больше второго, 0 — если они равны, и произвольным отрицательным числом в противном случае.
Оператор «космический корабль» возвращает не значение типа int, а объект, принадлежащий к одной из категорий сравнения, чьё значение отражает вид отношения между сравниваемыми объектами. Существует три основных категории:
Категории более сильного порядка могут неявно приводиться к категориям более слабого порядка (т.е. strong_ordering приводимо к weak_ordering). При этом текущий вид отношения сохраняется (т.е. strong_ordering::equal превращается в weak_ordering::equivalent).
Значения категорий сравнения можно сравнивать с литералом 0 (не с любым int и не с int, равным 0, а просто с литералом 0) с помощью одного из шести операторов сравнения:
Именно благодаря сравнению с литералом 0 мы можем реализовывать операторы отношения: a @ b эквивалентно (a b) @ 0 для каждого из таких операторов.
Например, 2 4) подходит намного лучше, чем оператор b) b даст значение partial_ordered::unordered, а partial_ordered::unordered может вернуть больше разновидностей значений: так, категория partial_ordering содержит четыре возможных значения. Значение типа bool может быть только true или false, поэтому раньше мы не могли различать сравнения упорядоченных и неупорядоченных значений.
Для большей ясности рассмотрим пример отношения частичного порядка, не связанный с числами с плавающей запятой. Допустим, мы хотим добавить типу int состояние NaN, где NaN — это просто значение, которое не образует упорядоченной пары ни с одним задействованным значением. Сделать это можно, используя для его хранения std::optional:
Новые возможности операторов
Каждый из базовых и производных операторов получил новую способность, о чём я скажу пару слов далее.
Обращение базовых операторов
В качестве примера возьмём тип, который может сравниваться только с int:
С точки зрения старых правил, нет ничего удивительного в том, что выражение a == 10 работает и вычисляется как a.operator==(10).
Но как насчёт 10 == a? В C++17 это выражение считалось бы явной синтаксической ошибкой. Не существует такого оператора. Чтобы такой код заработал, пришлось бы писать симметричный operator==, который бы сначала брал значение int, а затем A… а реализовывать это пришлось бы в виде свободной функции.
В C++20 базовые операторы могут быть обращены. Для 10 == a компилятор найдёт кандидат operator==(A, int) (на самом деле это функция-член, но для наглядности я пишу её здесь как свободную функцию), а затем дополнительно — вариант с обратным порядком параметров, т.е. operator==(int, A). Этот второй кандидат совпадает с нашим выражением (причём идеально), так что его мы и выберем. Выражение 10 == a в C++20 вычисляется как a.operator==(10). Компилятор понимает, что равенство симметрично.
Теперь расширим наш тип так, чтобы его можно было сравнивать с int не только через оператор равенства, но и через оператор упорядочения:
Однако 42 a вычисляется НЕ как a.operator (42). Так было бы неправильно. Это выражение вычисляется как 0 a.operator (42). Попробуйте сами догадаться, почему эта запись — правильная.
Важно отметить, что никаких новых функций компилятор не создаёт. При вычислении 10 == a не появился новый оператор operator==(int, A), а при вычислении 42 a не появился operator (int, A). Просто два выражения переписаны через обращённые кандидаты. Повторю: никаких новых функций не создаётся.
Также обратите внимание, что запись с обратным порядком параметров доступна только для базовых операторов, а для производных — нет. То есть:
Переписывание производных операторов
Вернёмся к нашему примеру со структурой A:
Заметим, что, как и в случае с обращением, компилятор не создаёт никаких новых функций для переписанных кандидатов. Они просто по-другому вычисляются, а все трансформации проводятся только на уровне исходного кода.
Вышесказанное приводит меня к следующему совету:
ТОЛЬКО БАЗОВЫЕ ОПЕРАТОРЫ: В своём типе определяйте только базовые операторы (== и ).
Поскольку базовые операторы дают весь набор сравнений, то и определять достаточно только их. Это значит, что вам понадобится только 2 оператора для сравнения однотипных объектов (вместо 6, как сейчас) и только 2 оператора для сравнения разнотипных объектов (вместо 12). Если вам нужна только операция равенства, то достаточно написать 1 функцию для сравнения однотипных объектов (вместо 2) и 1 функцию для сравнения разнотипных объектов (вместо 4). Класс std::sub_match представляет собой крайний случай: в C++17 в нём используется 42 оператора сравнения, а в C++20 — только 8, при этом функциональность никак не страдает.
Так как компилятор рассматривает также обращённые кандидаты, все эти операторы можно будет реализовывать как функции-члены. Больше не придётся писать свободные функции только ради сравнения разнотипных объектов.
Особые правила поиска кандидатов
Как я уже упоминал, поиск кандидатов для a @ b в C++17 происходил по следующему принципу: находим все операторы operator@ и выбираем из них наиболее подходящий.
В C++20 используется расширенный набор кандидатов. Теперь мы будем искать все operator@. Пусть @@ — это базовый оператор для @ (это может быть один и тот же оператор). Мы также находим все operator@@ и для каждого из них добавляем его обращённую версию. Из всех этих найденных кандидатов выбираем наиболее подходящий.
Заметьте, что перегрузка оператора разрешается за один-единственный проход. Мы не пытаемся подставлять разные кандидаты. Сначала мы собираем их все, а затем выбираем из них наилучший. Если такого не существует, поиск, как и раньше, заканчивается неудачей.
Теперь у нас гораздо больше потенциальных кандидатов, а значит и больше неопределённости. Рассмотрим следующий пример:
Для снятия этой неопределённости введены два дополнительных правила. Необращённые кандидаты предпочтительнее обращённых; непереписанные кандидаты предпочтительнее переписанных. Тогда получается, что x.operator!=(y) «главнее» !x.operator==(y), а тот «главнее» !y.operator==(x). Этот принцип согласуется со стандартными правилами, по которым «побеждает» наиболее точный вариант.
Ещё одно замечание: на этапе поиска нас не интересует тип возвращаемого значения кандидатов operator@@. Мы просто находим их. Нас интересует только, являются ли они наилучшим выбором или нет.
Неудачный исход при поиске теперь тоже выглядит по-другому. Если наилучший кандидат — переписанный или обращённый (например, мы написали x y) y возвращает void или какой-то иной тип, потому что мы вообще пишем на DSL), то программа считается некорректной. Возвращаться и искать другой подходящий вариант мы уже не будем. В случае с операцией равенства мы принимаем, что никакой тип возвращаемого значения кроме bool не совместим с переписанными кандидатами (логика здесь такая: если operator== не возвращает bool, можем ли мы считать такую операцию операцией равенства?)
Для выражения d1 d2) b
Определение сравнений для использования по умолчанию
Среди прочего в C++17 раздражает необходимость подробно расписывать поэлементные лексикографические сравнения. Это занятие утомительно и чревато ошибками. Напишем полный набор операторов для линейно упорядоченного типа с тремя членами:
Ещё лучше было бы использовать какой-нибудь std::tie(), но это всё равно утомительно.
Теперь давайте попробуем написать ту же структуру, следуя моему совету: определять только базовые операторы:
Темы будущих статей
В этой статье мы рассмотрели основы сравнений в C++20: как работают синтетические кандидаты и как они находятся. Мы также коротко рассмотрели трёхстороннее сравнение и особенности его реализации. У меня в запасе есть ещё несколько интересных тем, которые тоже стоит осветить, но я стараюсь писать не слишком длинные статьи, так что ждите новых постов.
Примечание переводчика
Команда PVS-Studio с интересом познакомилась с этой статьей, так как нам в ближайшее время предстоит реализовать поддержку нового оператора в анализаторе. А поскольку статья очень полезная и хорошо всё объясняет, мы решили сделать её перевод для хабра-сообщества. На наш взгляд, это очень нужное нововведение языка, так как по нашему опыту операторы сравнения очень часто содержат ошибки (см. статью «Зло живёт в функциях сравнения»). Теперь С++ программистам жить станет проще и ошибок данного типа будет меньше.
Заодно возникла идея создать в PVS-Studio новую диагностику для поиска некорректно написанных операторов