1. Вступ
Здається, якщо компʼютер «розумний», то 0.1 + 0.2 має дорівнювати просто 0.3. Але це не зовсім так. Розберімося на простому прикладі.
double x = 0.1;
double y = 0.2;
double sum = x + y;
Console.WriteLine(sum); // Що виведе програма?
А тепер спробуйте порівняти з 0.3:
Console.WriteLine(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;
Console.WriteLine(sum); // 0.30000000000000004
Console.WriteLine(sum == 0.3); // False
Компʼютер вивів не 0.3, а 0.30000000000000004. Різниця мізерна, але якщо ви працюєте, скажімо, з фінансами, це вже критично.
Приклад 2. Ітеративне додавання
double result = 0;
for (int i = 0; i < 10; i++)
{
result += 0.1;
}
Console.WriteLine(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)
{
Console.WriteLine("Майже рівно!"); // Так порівнювати безпечніше
}
Тут ми кажемо: «Якщо різниця між числами менша за одну мільйонну, вважаємо, що числа рівні».
4. Спеціальні значення double: Infinity, NaN, -Infinity
Тип double зберігає не лише числа, а й особливі значення. Вони виникають у ситуаціях, які в елементарній математиці зазвичай «заборонені».
Нескінченність (Infinity)
Що буде, якщо поділити 1 на 0?
double result = 1.0 / 0.0;
Console.WriteLine(result); // Infinity
У C# (як і в багатьох мовах) ділення на 0 для double не спричиняє винятку. Замість цього результат має спеціальне значення — додатна нескінченність.
Відʼємна нескінченність (-Infinity)
Якщо поділити відʼємне число на 0, ви дістанете відʼємну нескінченність:
double result = -1.0 / 0.0;
Console.WriteLine(result); // -Infinity
«Не число» (NaN — Not a Number)
Наприклад, якщо спробувати обчислити корінь із відʼємного числа:
double result = Math.Sqrt(-1);
Console.WriteLine(result); // NaN
Або результат ділення 0.0 / 0.0:
double result = 0.0 / 0.0;
Console.WriteLine(result); // NaN
NaN — це «значення, яке не є числом».
Перевірка спеціальних значень
У C# є методи для перевірки спеціальних значень:
Console.WriteLine(double.IsInfinity(result)); // true, якщо нескінченність
Console.WriteLine(double.IsNaN(result)); // true, якщо NaN
Таблиця: як double реагує на нетипові операції
| Операція | Результат | Що зберігається в double |
|---|---|---|
|
Infinity | +∞ |
|
-Infinity | -∞ |
|
NaN | Не число |
|
NaN | Не число |
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ