Отличный вопрос! Вы попали в самую известную «ловушку» для начинающих (и не только) программистов.
Короткий ответ: Подвох есть. 0.1 + 0.2 НЕ будет равно 0.3.
В большинстве языков программирования (JavaScript, Python, Java, C++, C# и т.д.) результат будет примерно таким:
0.30000000000000004
Почему так происходит? Аналогия для понимания
Представьте, что вы пытаетесь записать дробь 1/3 в десятичной системе. У вас получится бесконечное число: 0.333333...
Если вам нужно сложить 1/3 + 1/3 + 1/3, и вы возьмете обрезанные значения, например, до трех знаков после запятой, то получится:
0.333 + 0.333 + 0.333 = 0.999
Это очень близко к 1, но не равно 1. Произошла небольшая ошибка округления, потому что мы не можем точно представить 1/3 в десятичной системе с конечным числом знаков.
С компьютерами происходит ровно то же самое, только они используют двоичную систему (состоящую из 0 и 1), а не десятичную.
Техническое объяснение
- Двоичная система: Компьютеры хранят все числа в двоичном виде. Целые числа (4, 15, 100) переводятся в двоичную систему идеально.
- Проблема с дробями: Дробные числа, которые выглядят просто и конечно в нашей десятичной системе (например,
0.1или1/10), в двоичной системе могут стать бесконечными, повторяющимися дробями.0.1(в десятичной) =0.0001100110011...(в двоичной)0.2(в десятичной) =0.0011001100110...(в двоичной)
- Ограниченная память: Компьютер не может хранить бесконечное число цифр. Он обрезает эту двоичную дробь на определенном знаке (например, на 52-м знаке для стандартного 64-битного числа).
- Ошибка округления: Из-за этого обрезания, числа
0.1и0.2хранятся в памяти не как точные0.1и0.2, а как их очень близкие двоичные приближения. - Итог: Когда вы складываете эти два не совсем точных числа, результат их сложения тоже не совсем точный. При переводе обратно в привычную нам десятичную систему мы и видим эту крошечную погрешность:
0.30000000000000004.
Это поведение стандартизировано и называется стандартом IEEE 754 для чисел с плавающей запятой.
И что с этим делать? (Практические выводы)
-
НИКОГДА не сравнивайте числа с плавающей запятой на точное равенство.
// ПЛОХО: этот код скорее всего не сработает if (0.1 + 0.2 === 0.3) { console.log("Равно!"); // Это сообщение не будет выведено }Вместо этого проверяют, что разница между числами очень мала (меньше некой погрешности, "эпсилон").
// ХОРОШО: if (Math.abs((0.1 + 0.2) - 0.3) < 0.000001) { console.log("Практически равно!"); // А это сработает } -
Для финансовых расчетов используйте специальные типы данных.
- Тип
Decimal: Во многих языках есть специальные классы (Decimal,BigDecimal), которые хранят числа в десятичном формате и избегают этой проблемы. Они медленнее, но точны. - Храните деньги в копейках/центах: Самый надежный способ — вести все расчеты в наименьших денежных единицах (копейках, центах) с помощью обычных целых чисел, которые абсолютно точны. А при выводе на экран просто делить на 100.
- Тип
Резюме: Да, подвох есть. Это фундаментальная особенность работы компьютеров с дробными числами, о которой должен знать каждый разработчик.