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

  Юрій  | 

  Оновл. 20 Лип 2021  | 

 213

У мові 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.

Функція isAlmostEqual() з вищенаведеного прикладу порівнює різницю (а − 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 Зірок (3 оцінок, середня: 5,00 з 5)
Loading...

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

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