Урок №45. Оператори порівняння

  Юрій  | 

  Оновл. 29 Кві 2020  | 

 116

У C++ є 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.0000001 підходить для чисел які знаходяться близько від 1.0, але буде занадто великим для чисел типу 0.0000001 і занадто малим для чисел типу 10000. Це означає, що кожен раз, при виконанні функції, нам потрібно буде вибирати найбільш відповідний вхідним даним функції епсилон.

Дональд Кнут, відомий вчений, запропонував наступний спосіб у своїй книзі “Искусство программирования, том 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). Таким чином, ми вирішуємо випадки, коли а і b — нульові значення або близькі до нуля. Якщо це не так, то ми повертаємося до алгоритму Кнута.

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

Результат:

1
0
1

З вдало підібраним absEpsilon, функція approximatelyEqualAbsRel() обчислює нульові значення правильно.

Порівняння чисел типу з плаваючою крапкою — складна тема, і немає одного ідеального алгоритму, який підійде в будь-якій ситуації. Однак, для більшості випадків, з якими ви будете стикатися, функції approximatelyEqualAbsRel() має бути достатньо.

Оцінити статтю:

1 Зірка2 Зірки3 Зірки4 Зірки5 Зірок (1 оцінок, середня: 5,00 з 5)
Loading...

Залишити відповідь

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