В вашем терминале Python попробуйте выполнить это

Странно не правда ли? Ответ должен был быть True. Каждый программист хоть раз сталкивался с этой проблемой. Если нет, то они обязательно столкнутся с этим в будущем. Что касается вопроса «Почему это происходит?», во-первых, мы должны понять, как числа с плавающей запятой представлены внутри компьютера.

На самом деле это не ошибка в Python или любом другом языке. Это вызвано несоответствием между различными системами счисления. Читайте дальше для дальнейшего объяснения.

Во-первых: что такое числа с плавающей запятой?

Числа с плавающей запятой используются для представления дробных чисел (сокращенно дроби) и используются в большинстве инженерных и технических расчетов. Например, 3.256, 2.1 и 0.0036 являются числами с плавающей запятой. Таким образом, 0.1 может быть выражено в дробной форме как 1/10 и 0.2 как 2/10.Когда мы возьмем сумму этих двух дробей, мы получим 3/10, что равно 0.3.

Довольно просто, не так ли? Это потому, что мы используем base-10 или decimal систему счисления для повседневных расчетов. Система счисления base-10 использует числа от 0 до 9 для представления и вычислений. Таким образом 0.1 + 0.2 = 0.3. Когда дело доходит до компьютеров, они понимают языки программирования более высокого уровня, такие как Python, Java, C, преобразуя их и сохраняя в системе счисления base-2 или системе счисления binary, состоящей из 0s и 1s.

Base-10 против Base-2: разборки!

Рассмотрим 1/3 в десятичном формате. Десятичная дробь — это рекурсивная дробь, равная 0.3333333. Точно так же 2/3 равно 0.66666666. Взяв сумму двух, мы получим 0.99999999, а не 1. Поскольку основанием десятичной системы является 10, можно точно представить только дроби со знаменателем, содержащим один из простых множителей числа 10, т.е. 2 и 5. Точно так же в двоичной системе могут быть точно представлены только дроби со знаменателем, содержащим только простой множитель 2, который сам является 2. К сожалению, для большинства десятичных чисел нет точного представления в двоичной системе счисления.

Назад к 0.1 + 0.2

В base-2 1/10 — это бесконечно повторяющаяся дробь 0.0001100110011001100110011001100110011001100110011.... То же самое и с 2/10, который равен 0.001100110011001100110011001100110011.... Остановка на любом конечном числе приводит к приближению. Сегодня на большинстве машин числа с плавающей запятой аппроксимируются с использованием стандарта IEEE с плавающей запятой двойной точности, который использует 64 bits для представлений.

Первый бит — это бит знака, S, следующие 11 бит — это биты экспоненты, E, а последние 52 бита — это дробь F. Соответственно, ближайшим десятичным значением усеченного значения 0.1 будет 0.1000000000000000055511151231257827021181583404541015625, что близко к 0.1, но не совсем равно. Точно так же для 0.2 это 0.200000000000000011102230246251565404236316680908203125.

Большинство языков программирования возвращают 0.30000000000000004 как сумму 0.1 и 0.2. Но что интересно, вы можете видеть, что 0.1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102230246251565404236316680908203125 не равно 0.30000000000000004. Это связано с тем, что когда машина добавляет 0.1 и 0.2, она видит только усеченные двоичные значения 0.1 и 0.2. И неудивительно, что наилучшим приближением к сумме этих двух двоичных чисел является значение чуть выше 0.3: примерно 0.30000000000000004.

Но подождите, это еще не все!

Эти ошибки аппроксимации также появляются, когда вы пытаетесь округлить числа! Вы ожидаете, что округление 2.675 до двух знаков после десятичной точки вернет 2.68. Но нет! Он возвращает 2.67, поскольку 2.675, преобразованное в двоичное число с плавающей запятой, снова заменяется двоичным приближением, точное значение которого равно 2.67499999999999982236431605997495353221893310546875, что ближе к 2.67, чем 2.68.

Решение для Python

При работе с десятичными вычислениями считается хорошей практикой всегда округлять десятичные дроби до количества позиций, которое требуется для программирования. В сценариях, где важно, как округляются десятичные дроби или точность арифметики с плавающей запятой, в Python есть модуль с именем decimal.

Если вы не занимаетесь проектами, которые определяют траектории полета или целостность атомной электростанции, или обрабатываете финансовую информацию компаний, аппроксимации с плавающей запятой не представляют большой проблемы. Целесообразно помнить об этом, а не забывать об этом, когда вы выполняете вычисления с плавающей запятой на своем языке.

Удачного кодирования!