Порівняння чисел з плаваючою крапкою (десяткових дробів)

Багато дій у програмуванні та математиці є однаковими або дуже схожими. Наприклад, дія додавання цілих чисел. У математиці та в програмуванні (не тільки у а й в інших мовах) дія буде давати результат

Якщо б вас запитали чи дії будуть давати однаковий результат, то щоб ви відповіли?

Правильна відповідь буде «так, ці дії даватимуть однаковий результат». Адже, і

А, якщо, вас запитати чи наступні дії дадуть однаковий результат і то, що ви відповісте?

Давайте порахуємо:

0.2 + 0.2 + 0.2 + 0.2 + 0.2 = 1.0

0.5 + 0.5 = 1.0

Отже, отримали, що в математиці ці дії дають однаковий результат, бо

У мові С++ при порівнянні таких сум ви можете отримати два результат:

1. Числа будуть однаковими. За умови, якщо ви використовуєте нові стандарти Також, результати можуть бути однаковими при використанні типу змінних

2. Числа вважатимуться різними. За умови, якщо ви використовуєте старіші стандарти Також, результати можуть бути однаковими при використанні типу змінних

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

    
        #include <iostream>

        int main()
        {
            double a = 0, b = 0;
            //зробіть свої змінні іншого типу і спробуйте вгадати результат
            //float a = 0, b = 0;

            for (int i = 0; i < 10; i++)
                a += 0.1;

            for (int i = 0; i < 20; i++)
                b += 0.05;

            if (a > b)
                std::cout << a << " > " << b << std::endl;
            else if (a < b)
                std::cout << a << " < " << b << std::endl;
            else
                std::cout << a << " = " << b << std::endl;

            return 0;
        }
    

Також, в математиці порівнюючи числа і ми можемо сказати, що вони є рівними за умови, якщо не вимагається велика точність.

У мові програмування для порівняння таких чисел використовують в основному два способи.

1. Порівняння точністю.

2. Порівняння відсотком.

Розглянемо ці методи.

Порівняння точністю. Це доволі простий та примітивний метод. Ми вказуємо якусь точність до якої нам важливо порівняти числа. Після чого шукаємо різницю чисел, що порівнюються та беремо її абсолютне значення (тобто, модуль числа). Якщо дане число (різниця) є меншою за обрану точність, то ці числа вважаються рівними.

Звісно, потрібно враховувати знак даних чисел, якщо ви не порівнюєте їх по модулю! Тобто, якщо одне з ваших чисел є додатним, а інше то для порівняння вам варто використовувати модулі цих чисел.

Даний метод зручно використовувати, коли ми маємо малі числа. Але, якщо числа є великими, от цей метод вже не буде ефективним.

    
        #include <iostream>
        #include <math.h>

        //аргументами даної функції є два числа які ми будемо порівнювати
        //та точність з якою ми будемо їх порівнювати
        //даний спосіб добре працює із малими числами
        //0.000001 можна написати як 1e-6
        void fun(double num1, double num2, double eps = 0.000001)
        {
            //нам необхідно порівнювати абсолютне значення різниці
            //тобто: abs(num1 - num2)
            //але, в даній програмі ми порівнюємо числа лише
            //за абсолютним значенням. тому, є додаткові модулі
            if (abs(abs(num1) - abs(num2)) <= eps)
                std::cout << " == \n";
            else
                std::cout << " != \n";
        }

        int main()
        {
            double num1 = 23.98765434, num2 = 23.98765421;

            //функція з базовою точністю
            fun(num1, num2);
            //функція із заданою точністю
            fun(num1, num2, 0.00000001);

            return 0;
        }
    

Отже, ми вже вияснили, що попередній метод є робочим але не ідеальним. Адже, вам доведеться підбирати точність самостійного кожного разу. Тому, також виконують порівняння відсотком (метод Кнута).

Порівняння відсотком. Даний метод схожий до попереднього але в нього різниця між числами не просто порівнюється з якоюсь точністю, а з відсотком від більшого числа.

Запустіть наступну програму та спробуйте розібратися в ній.

    
        #include <iostream>
        #include <math.h>

        //аргументами даної функції є два числа які ми будемо порівнювати
        //та точність з якою ми будемо їх порівнювати
        //даний спосіб погано працює із числами близькими до "0"
        //ми беремо абсолютне значення різниці між числами "abs(num1 - num2)"
        //також, обираємо більше абсолютне значення з чисел "num1" і "num2"
        //abs(num1) < abs(num2) ? num2 : num1
        //після чого більше з чисел ми множимо на точність "eps"
        //"більше число * eps" виступає як відсоткове значення за яким ми
        //будемо робити порівняння
        void fun(double num1, double num2, double eps = 0.001)
        {
            if (abs(num1 - num2) <= (abs(num1) < abs(num2) ? num2 : num1) * eps)
                std::cout << " == \n";
            else
                std::cout << " != \n";

            //щоб візуально побачити як це працює, то виведемо наші числа:
            //цей код в основній функції не потрібен
            std::cout << "num 1 = " << num1
                      << "\nnum 2 = " << num2
                      << "\nabs(num 1 - num 2) = " << abs(num1 - num2)
                      << "\n(abs(num1) < abs(num2) ? num2 : num1) * eps = " << (abs(num1) < abs(num2) ? num2 : num1) * eps
                      << std::endl << std::endl;
        }

        int main()
        {
            double num1 = 0.0, num2 = 0.0;

            for (int i = 0; i < 10; i++)
                num1 += 0.2;

            for (int i = 0; i < 20; i++)
                num2 += 0.1;

            //порівнюємо числа які не є близькими до нуля
            //програма буде працювати добре
            fun(num1, 2.0);
            fun(num1, num2);
            //порівнюємо числа близькі до нуля. тут буде помилка
            fun(num1 - 2.0, 0.0);

            return 0;
        }
    

Як було показано цей метод погано працює при порівняні чисел близьких до нуля. Тому, на практиці програмісти їх в одну функцію.

    
        #include <iostream>
        #include <math.h>

        bool fun(double num1, double num2, double eps = 0.001)
        {
            //якщо наші числа є близькими до нуля і їх
            //абсолютна різниця є менша за точність, то
            //програма видає, що числа є рівні
            if(abs(abs(num1) - abs(num2)) <= eps)
                return true;

            //коли в нас будуть числа не близькі до нуля
            //і перша перевірка провалиться, то ще перевіряємо
            //ці числа за алгоритмом Кнута
            if (abs(num1 - num2) <= (abs(num1) < abs(num2) ? num2 : num1) * eps)
                return true;

            return false;
        }

        int main()
        {
            double num1 = 0.0, num2 = 0.0;

            for (int i = 0; i < 10; i++)
                num1 += 0.2;

            for (int i = 0; i < 20; i++)
                num2 += 0.1;

            std::cout << fun(num1, 2.0) << std::endl;
            std::cout << fun(num1, num2) << std::endl;

            std::cout << fun(num1 - 2.0, 0.0) << std::endl;

            return 0;
        }