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:
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 |
|---|---|---|
|
Infinity | +∞ |
|
-Infinity | -∞ |
|
NaN | Не число |
|
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. Це може призвести до тихих і важковиявних помилок — результат є, але він не завжди той, якого ви очікували.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ