Во многих языках программирования мы обнаружим странное явление: при вычислении 0.1 + 0.2 результат не равен 0.3. Например, C, C++, JavaScript, Python, Java, Ruby и т. д. — все имеют эту проблему.

По этой причине кто-то даже создалhttps://0.30000000000000004.com/сайт. На этом сайте мы можем узнать, какие языки программирования при вычислении 0.1 + 0.2 дают результат не 0.3, а 0.30000000000000004.

Мы можем использовать JavaScript для демонстрации. Вычисление 0.1 + 0.2 даёт результат не 0.3, а 0.30000000000000004.

Конечно, если использовать это как условное выражение, оно также возвращает false:

Есть и другие примеры:

6 * 0.1 = 0.6 但计算机显示为 0.6000000000000001​
0.11 + 0.12 = 0.23 但计算机显示为 0.22999999999999998​
0.1 + 0.7 = 0.8 但计算机显示为 0.7999999999999999​
0.3+0.6 = 0.9 但计算机显示为0.8999999999999999

Однако следующие выражения могут дать желаемый результат:

Почему так?

Проще говоря, компьютеры используют двоичные числа с плавающей запятой, тогда как мы, люди, используем десятичные числа с плавающей запятой.

В двоичной системе числа с плавающей запятой обычно представляются с использованием стандарта IEEE 754. Десятичные дроби, такие как 0.1, 0.2 или 0.3, не могут быть точно представлены, потому что используется двоичный формат чисел с плавающей запятой.

О стандарте IEEE 754 см.:https://zh.wikipedia.org/wiki/IEEE_754

Основной принцип

В десятичной системе дробь может быть представлена точно, если в её знаменателе используются простые множители основания (10).

Простыми множителями числа 10 являются 2 и 5.

Следовательно, 1/2, 1/4, 1/5 (0.2), 1/8 и 1/10 (0.1) могут быть представлены точно, потому что в их знаменателях используются простые множители числа 10.

Однако 1/3, 1/6 и 1/7 являются бесконечными периодическими десятичными дробями, потому что в их знаменателях используются простые множители 3 или 7.

В двоичной системе (системе, используемой компьютерами) дробь может быть представлена точно, если её знаменатель использует простые множители основания (2).

2 — единственный простой множитель числа 2.

Следовательно, 1/2, 1/4 и 1/8 могут быть представлены точно, потому что их знаменатели используют простые множители числа 2.

Однако 1/5 (0.2) или 1/10 (0.1) являются бесконечными периодическими десятичными дробями, потому что в их знаменателях используются простые множители 5 или 10.

Поэтому, когда мы пытаемся представить десятичную дробь, такую как 0.1, компьютер использует приближение. Это приближение реализуется путём преобразования бесконечно повторяющейся двоичной дроби в представление с плавающей точкой с конечным числом битов.

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

Например, приближённое представление 0.1 в двоичной системе может быть 0.000110011001100..., но в представлении компьютера с плавающей точкой оно может быть усечено или округлено до 0.00011001100110. Это приводит к тому, что 0.1 + 0.2 в компьютере не обязательно равно 0.3, а немного отклоняется.

0.1 — это одна десятая (1/10). Чтобы получить двоичное представление 0.1 (то есть его двоичную форму), нам нужно использовать двоичное деление столбиком, то есть разделить двоичное число 1 на двоичное 1010 (то есть 1/1010), следующим образом:

Следовательно, 0.1 в двоичной системе представляется как 0.0001100110011001100110011... (бесконечно повторяющееся).

Этот бесконечно повторяющийся шаблон 0011 будет продолжаться вечно, потому что двоичная система может только так приблизить десятичное число 0.1.

В реальной компьютерной системе эта бесконечно повторяющаяся десятичная дробь усекается до конечного числа битов для хранения и вычислений. Это приводит к возможной потере точности при выполнении двоичной арифметики с плавающей точкой в компьютере, из-за чего сумма 0.1 и 0.2 не в точности равна 0.3.

Преобразование десятичных дробей в двоичные

Существует также более простой для понимания метод (метод умножения на 2 и взятия целой части). Например, чтобы преобразовать десятичную дробь 0.875 в двоичное число, нужно просто умножать дробную часть на 2, затем извлекать целую часть, пока дробная часть не станет 0.

Расположите извлечённые выше целые части, чтобы получить результат 111, который становится двоичным представлением..875。

Двоичное число 1101.111, целая часть которого 1101, а дробная часть 111, является результатом преобразования десятичного числа 13.875 в двоичное.

Используя вышеописанный метод, преобразование десятичной дроби 0.1 в двоичную даёт:

0.1 = 0.0001100110011001100110011001100110011001100110011001101...

Преобразование десятичной дроби 0.2 в двоичную даёт:

0.2 = 0.001100110011001100110011001100110011001100110011001101...


Решения

1. Сначала преобразуйте десятичную дробь в целое число

Сначала можно преобразовать десятичные дроби в целые числа, сложить их, а затем преобразовать обратно в десятичные дроби, как в следующем примере:

(0.1*10 + 0.2*10)/10

2. Используйте метод toFixed()

Метод toFixed() может преобразовать число в строковое представление с указанным количеством знаков после запятой.

Ниже приведён пример использования метода toFixed() для решения проблем точности с плавающей запятой:

let sum = 0.1 + 0.2;
console.log(sum.toFixed(2)); // 输出: 0.30

Следует отметить, что хотя метод toFixed() решает проблему с точки зрения отображения, он не изменяет фактическое значение числа; он изменяет только его представление.

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

3. Используйте библиотеку decimal.js

При работе с проблемами точности с плавающей запятой в JavaScript использование библиотеки decimal.js является более точным и надёжным решением.

decimal.js — это библиотека десятичной арифметики произвольной точности, позволяющая избежать неточностей операций с плавающей запятой в нативном JavaScript.

Адрес GitHub:https://github.com/MikeMcl/decimal.js

Сначала вам нужно импортировать библиотеку decimal.js в ваш проект.

Установка с помощью npm:

npm install decimal.js

Импорт в HTML:

<script src="../cdnjs.cloudflare.com/ajax/libs/decimal.js/10.2.0/decimal.min.js"></script>

Затем вы можете использовать decimal.js для работы с операциями с плавающей запятой. Вот пример:

Пример

// Импортируйте конструктор Decimal
const Decimal = decimal. Decimal;
​
// Создайте два объекта Decimal
let a = new Decimal('0.1');
let b = new Decimal('0.2');
​
// Выполните сложение
let sum = a.plus(b);
​
// Вывести результат
console.log(sum.toString()); // Вывод: 0.3

Результат, полученный с использованием библиотеки decimal.js, точно равен 0.3, а не приближённому значению из нативного JavaScript.