JavaRush /Курси /JAVA 25 SELF /Проблеми точності та спеціальні значення

Проблеми точності та спеціальні значення

JAVA 25 SELF
Рівень 6 , Лекція 3
Відкрита

1. Вступ

Здається: якщо компʼютер «розумний», то 0.1 + 0.2 має дорівнювати 0.3. Але це не зовсім так. Розгляньмо простий приклад.


double x = 0.1;
double y = 0.2;
double sum = x + y;
System.out.println(sum); // Що виведе програма?
Додавання дійсних чисел: очікуємо 0.3, а що насправді?

А тепер спробуйте порівняти з 0.3:

System.out.println(sum == 0.3); // А тут буде true чи false?

Якщо побачите false, не дивуйтеся.

Причина криється в поданні чисел у памʼяті

Компʼютери працюють із числами в двійковій системі. Але далеко не всі десяткові дроби можна подати скінченною двійковою дробою, так само, як 1/3 не можна точно записати десятковою дробою (0.333...). Наприклад, 0.1 у двійковій системі має нескінченну дробову частину, тож її доводиться округлювати під час зберігання.

Простими словами: double інколи виглядає так, ніби зберігає число точно, але насправді — лише дуже близьке наближення.

2. Які несподівані ефекти виникають під час арифметики з double?

Подивімося на практичні приклади.

Приклад 1. Класичний випадок 0.1 + 0.2

double a = 0.1;
double b = 0.2;
double sum = a + b;
System.out.println(sum);            // 0.30000000000000004
System.out.println(sum == 0.3);     // false

Компʼютер вивів не 0.3, а 0.30000000000000004. Різниця дуже мала, але якщо ви працюєте, наприклад, із фінансами — це вже може бути критично.

Приклад 2. Ітеративне додавання

double result = 0;
for (int i = 0; i < 10; i++)
{
    result += 0.1;
}
System.out.println(result); // 0.9999999999999999

Очікували 1.0 — отримали трохи менше. Причина — знову в округленнях усередині double.

Чому це важливо в реальних задачах

Багато хто думає: «Мала похибка — яка різниця?» Розгляньмо приклад зі світу платежів.

Припустімо, ваш інтернет‑банк обробляє 100 транзакцій по 0.1 євро. Якщо програма втрачає по одній стотисячній євро на кожну транзакцію, то у масштабах банку це вже перетворюється на реальні збитки. І бухгалтер неодмінно запитає: «Де наші гроші?!»

3. Як правильно порівнювати дійсні числа

Оскільки double часто не може зберегти точне значення, яке ви очікуєте, пряме порівняння через == може підвести. Натомість прийнято порівнювати модуль різниці чисел із деяким дуже малим числом (epsilon).

Приклад порівняння з допуском

double a = 0.1 + 0.2;
double b = 0.3;
double epsilon = 0.000001;

if (Math.abs(a - b) < epsilon)
{
    System.out.println("Майже рівні!"); // Так порівнювати безпечніше
}

Тут кажемо: «Якщо різниця між числами менша за одну мільйонну, вважаємо їх рівними».
Примітка: функція Math.abs(value) повертає абсолютне значення (модуль) переданого числа.

4. Спеціальні значення double: Infinity, NaN, -Infinity

Тип double зберігає не лише числа, а й спеціальні значення. Вони зʼявляються в ситуаціях, які в математиці вважають некоректними.

Нескінченність (Infinity)

Що буде, якщо поділити 1 на нуль?

double result = 1.0 / 0.0;
System.out.println(result); // Infinity

У Java (і в багатьох інших мовах) ділення на нуль для double не викликає винятку. Замість цього результат — спеціальне значення «додатна нескінченність».

Відʼємна нескінченність (-Infinity)

Якщо поділити відʼємне число на нуль, отримаємо відʼємну нескінченність:

double result = -1.0 / 0.0;
System.out.println(result); // -Infinity

«Не число» (NaN — Not a Number)

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

double result = Math.sqrt(-1);
System.out.println(result); // NaN

Або результат ділення 0.0 / 0.0:

double result = 0.0 / 0.0;
System.out.println(result); // NaN

NaN позначає значення, яке в реальних обчисленнях не є числом.

Перевірка на спеціальні значення

У Java є методи для перевірки спеціальних значень:

System.out.println(Double.isInfinite(result));    // true, якщо нескінченність
System.out.println(Double.isNaN(result));         // true, якщо NaN

Таблиця: як double реагує на незвичні операції

Операція Результат Що зберігається в double
1.0 / 0.0
Infinity +∞
-1.0 / 0.0
-Infinity -∞
0.0 / 0.0
NaN Не число
Math.sqrt(-1)
NaN Не число

5. Типові помилки під час роботи з дійсними числами

Помилка № 1: Порівняння дробових чисел через ==
Найпоширеніша помилка — спроба перевірити, чи рівні два обчислені числа з плаваючою комою через звичайне порівняння. Через накопичення помилок округлення ви майже завжди отримаєте несподіваний результат. Завжди використовуйте порівняння з допуском (epsilon).

Помилка № 2: Неочікувані NaN та Infinity в обчисленнях
Якщо не контролювати ділення на нуль або операції з відʼємними аргументами, у програмі можуть зʼявитися NaN або Infinity, які потім вплинуть на всі подальші розрахунки. Не забувайте перевіряти підозрілі значення за допомогою Double.isNaN() і Double.isInfinite() — це допоможе уникнути неприємних сюрпризів.

Помилка № 3: Використання NaN як маркера
Деякі початківці використовують NaN як спеціальне значення для позначення, наприклад: «якщо не знайдено — повернемо NaN». Але памʼятайте: NaN підступний, і порівняння через == не спрацює! Перевіряйте лише через спеціальні методи.

Помилка № 4: Очікування винятку під час ділення на 0.0
На відміну від ділення цілих чисел, ділення на 0.0 для double не викликає помилки, а повертає Infinity або NaN. Це може призвести до тихих і важковиявних помилок — результат є, але він не завжди той, якого ви очікували.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ