Почему в компьютере 0,1 + 0,2 ≠ 0,3
Один из самых удивительных моментов для начинающего программиста: компьютер, который умеет считать миллиарды операций в секунду, ошибается в сложении десятых. В Python 0.1 + 0.2 даёт 0.30000000000000004. Это не ошибка языка, а следствие того, как компьютер хранит дробные числа. Разбираемся, откуда берётся погрешность и как писать программы, чтобы она не мешала.
Для 8–11 классов, информатика и программирование.
Проверьте сами
print(0.1 + 0.2) print(0.1 + 0.2 == 0.3)
Первая строка выведет 0.30000000000000004, а вторая False: компьютер считает, что 0,1 + 0,2 не равно 0,3. То же самое произойдёт в JavaScript, Java, C++ и почти любом другом языке. Причина не в программе, а в самом способе хранения чисел.
Дроби в двоичной системе
Вспомним десятичные дроби. Дробь 1/3 нельзя записать конечной десятичной дробью: 0,333… без конца. А 1/4 можно: 0,25. Всё зависит от знаменателя: конечная десятичная дробь получается, только если в знаменателе одни двойки и пятёрки, ведь 10 = 2 · 5.
В двоичной системе основание 2. Конечными получаются только дроби, у которых в знаменателе одни двойки: 1/2 = 0,1₂, 1/4 = 0,01₂, 3/8 = 0,011₂. А 1/10 в знаменателе содержит пятёрку, поэтому в двоичной системе это бесконечная периодическая дробь:
0,1₁₀ = 0,0001100110011…₂
Компьютер не может хранить бесконечно много цифр. Обычно на число отводится 64 бита, и дробь обрезается. Получается не ровно 0,1, а очень близкое к нему число. С 0,2 то же самое. Когда складываются два приближённых числа, их маленькие ошибки складываются, и результат оказывается не ровно 0,3.
Как переводить числа между системами, мы подробно разобрали в статье о системах счисления.
Числа с плавающей точкой
Дробные числа в компьютере хранятся в формате с плавающей точкой, его описывает международный стандарт IEEE 754, принятый в 1985 году. Число записывается как мантисса, то есть значащие цифры, и порядок, показатель степени двойки. Это похоже на научную запись: 6,02 · 10²³.
Такой формат позволяет хранить и огромные числа, и крошечные, но точность ограничена: около 15–17 значащих десятичных цифр. Для большинства задач этого хватает с огромным запасом. Расстояние до Луны так можно записать с точностью до долей миллиметра.
Задача
Какие из этих дробей хранятся в компьютере точно: 0,5; 0,1; 0,25; 0,75; 0,3; 0,125?
Точно хранятся дроби со знаменателем-степенью двойки.
0,5 = 1/2, 0,25 = 1/4, 0,75 = 3/4, 0,125 = 1/8: точно.
0,1 = 1/10 и 0,3 = 3/10: в знаменателе пятёрка, хранятся приближённо.
Ответ: точно: 0,5; 0,25; 0,75; 0,125.
Как с этим жить в программах
Не сравнивайте дробные числа через ==
Вместо точного равенства проверяйте, что числа достаточно близки:
a = 0.1 + 0.2 print(abs(a - 0.3) < 1e-9)
Программа выведет True. Здесь 1e-9 это одна миллиардная: допустимая погрешность. В Python есть и готовая функция math.isclose(a, 0.3), которая делает то же самое.
Округляйте при выводе
print(round(0.1 + 0.2, 2))
Выведет 0.3. Округление при показе пользователю скрывает хвост погрешности, но сами вычисления остаются приближёнными.
Деньги в копейках
Для денег приближённость недопустима: никто не хочет потерять копейку. Поэтому банковские программы хранят суммы в целых копейках или используют специальные десятичные типы. В Python есть модуль decimal, где 0.1 + 0.2 даёт ровно 0.3.
Когда погрешность опасна
Обычно погрешность незаметна, но если её накапливать миллионы раз, она вырастает. Известная история произошла в 1991 году во время войны в Персидском заливе. Система противоракетной обороны считала время в десятых долях секунды, и 0,1 хранилась в ней приближённо. После 100 часов непрерывной работы ошибка накопилась до трети секунды. Система промахнулась, и ракета попала в казарму, погибли 28 человек.
Поэтому в науке, медицине и технике программисты внимательно следят за точностью вычислений. Этим занимается отдельная область математики: численные методы.
Это пригодится на ЕГЭ
На ЕГЭ по информатике встречаются задачи, где программа работает с дробными числами: деление, корни, проценты. Если сравнивать результаты через ==, можно получить неверный ответ из-за погрешности. Надёжнее работать с целыми числами, где это возможно: вместо проверки x / 3 == 2 писать x == 6, вместо проверки, что корень из n целый, сравнивать квадрат целого числа с n.
Задача
Нужно проверить, является ли число n полным квадратом. Почему проверка через корень может подвести и как сделать надёжно?
Корень вычисляется приближённо: для очень больших n он может дать число вроде 99999999.99999999 вместо 100000000.
Надёжно: взять целый корень r = math.isqrt(n) и проверить r * r == n. Здесь все вычисления целые и точные.
Ответ: через целочисленный корень math.isqrt и проверку r * r == n.
Как ещё решать задания ЕГЭ кодом, рассказали в статье о переборе на Python.
Разберём на занятии
Занимаемся по всем школьным предметам с 4 по 11 класс: живые занятия, общая доска и домашка, которую проверяет тот же преподаватель.
Первое занятие бесплатное: можно посмотреть формат вместе с ребёнком и решить, подходит ли он.
Частые вопросы
Значит, компьютеру нельзя доверять вычисления?
Можно. Погрешность очень мала, около одной десятимиллионной доли процента. Для подавляющего большинства задач она не имеет значения. Важно только помнить о ней, когда сравниваете дробные числа или считаете деньги.
Почему в калькуляторе телефона 0,1 + 0,2 = 0,3?
Калькуляторы для людей считают в десятичной системе или округляют результат перед показом. Программисты делают так специально: пользователь не должен видеть странные хвосты из нулей и четвёрок. Таблицы вроде Excel тоже округляют при показе, хотя внутри хранят приближённое число.
А целые числа тоже хранятся с погрешностью?
Нет. Целые числа хранятся точно. А в Python целые числа ещё и без ограничения по размеру: можно посчитать 2 в степени 1000 без единой ошибки.