.1 + .2 === 0.30000000000000004 // true

Почему 0,1 + 0,2 ≠ 0,3 в JavaScript

Математика с плавающей запятой в JavaScript иногда немного нечеткая. Вот почему 0,1 + 0,2 ≠ 0,3 и что вы можете сделать, если вам нужна точность.

Если 1 + 2 = 3, то почему в JavaScript не 0,1 + 0,2 = 0,3? Ответ связан с информатикой и математикой с плавающей запятой.

Если вы никогда этого не делали, я бы посоветовал вам открыть консоль браузера и ввести 0.1 + 0.2, чтобы проверить результат.

Нет, вам не нужно настраивать свой браузер - именно так он и должен работать, согласно стандарту ECMAScript, который определяет язык программирования JavaScript:

Тип Number имеет ровно 18437736874454810627 (то есть 2 ^ 64 - 2 ^ 53 + 3) значений, представляющих значения IEEE 754 в 64-битном формате двойной точности, как указано в стандарте IEEE для двоичной арифметики с плавающей запятой - Спецификация языка ECMAScript

JavaScript представляет числовые значения, используя примитивный тип числа, и все числа JavaScript на самом деле являются значениями с плавающей запятой - даже целыми числами.

Ключевым моментом здесь является то, что JavaScript реализует Стандарт IEEE для арифметики с плавающей запятой. Посмотрим, что это значит.

Что происходит здесь?

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

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

В двоичном формате значения представлены в формате base-2 в виде последовательности нулей и единиц вместо привычных десятичных чисел base-10, с которыми мы обычно работаем.

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

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

Простые множители по основанию 10 - это 2 и 5, поэтому 1/2, 1/4, 1/5, 1/8 и 1/10 могут быть выражены чисто, но 1/3, 1/6, 1/7 и 1/9 - это повторяющиеся десятичные дроби.

Простые множители основания 2 равны 2, поэтому только 1/2 может быть представлена ​​чисто - и любое другое значение становится повторяющимся десятичным числом.

Это означает, что когда мы используем десятичное число с основанием 10, например 0,1 (1/10), оно может быть представлено одной цифрой в десятичном виде, но не в двоичном.

Единственные дробные числа, которые могут быть четко выражены в двоичном формате, - 0,5 (1/2). Попробуйте сами, используя Конвертер с плавающей запятой IEEE-754.

Плавающие точки тоже медленнее

Вообще говоря, поведение с плавающей точкой в ​​JavaScript отличается от поведения целых чисел. Они медленнее, например, в for циклах.

Давайте посмотрим на два тестовых примера с использованием jsPerf для проверки микропроизводительности:

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

Это происходит из-за дополнительной сложности этих чисел с плавающей запятой при хранении в двоичном формате, как объяснялось в последнем разделе.

Конечно, в вашей кодовой базе нет достаточно большой разницы, но это интересная особенность JavaScript.

Что делать, если вам нужна точность?

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

Хотя все числа JavaScript внутри представлены как значения с плавающей запятой, вы не столкнетесь с неточностями при работе с целочисленными значениями, по крайней мере, пока вы находитесь в пределах MAX_SAFE_INTEGER (2^53 - 1):

Один из подходов заключался бы в том, чтобы просто работать только в центах, например, представив значение 19,99 доллара в виде целого числа 1999.

Другой подход - создать объект для представления валюты и использовать целые числа под капотом. Например:

Многие библиотеки решили эту проблему более надежно, включая account.js, currency.js, money.js и Numeral.js.

Наконец, вы можете рассмотреть возможность использования примитивного типа BigInt, который может представлять сколь угодно большие целые числа (но не значения с плавающей запятой):

TypeScript также поддерживает BigInts, поэтому использование BigInts в TypeScript может быть хорошим вариантом, чтобы избежать случайной работы с данными с плавающей запятой.

Заключение

Я был очень удивлен, узнав, что 0,1 + 0,2 фактически должно быть равно 0,30000000000000004 в JavaScript из-за математики с плавающей запятой.

Это похоже на ошибку, ожидающую своего появления, но нет четкого решения, потому что спецификация ECMAScript требует, чтобы 0,1 + 0,2 ≠ 0,3.

К счастью, целочисленная математика позволяет избежать этих досадных ошибок округления, поэтому можно добиться точности, используя числа JavaScript - если вы придерживаетесь целых чисел.

Для произвольной точности или для того, чтобы убедиться, что у вас никогда не было десятичного значения, вы можете рассмотреть возможность использования нового примитивного типа JavaScript BigInt.

Вам также могут быть полезны библиотеки точная математика или math.js. Оба предназначены для выполнения точных вычислений с использованием JavaScript.

Удачного кодирования! 📏🖥️📐⌨️😄

дальнейшее чтение













  • Анри Виджая опубликовал в своем блоге Bashooka список замечательных вспомогательных библиотек:


Присоединяйтесь к моему списку рассылки, чтобы получить бесплатный доступ ко всем моим статьям на Medium.