0,1+0,2 !== 0,3?

JavaScript

предисловие

Как мы все знаем, JavaScript теряет точность при вычислении определенных операций с плавающей запятой, например, при вводе в консоли.0.1+0.2, результат0.30000000000000004вместо0.3,какова причина?

В мире есть два типа людей: те, кто понимает двоичный код, и те, кто не понимает.

Мы знаем, что все данные в компьютере в конечном счете хранятся в двоичном виде, и, конечно же, числа одинаковы. Поэтому, когда компьютер вычисляет0.1+0.2Когда фактическое вычисление представляет собой двоичный код этих двух чисел, хранящихся в компьютере, тогда0.1Сколько двоичных файлов хранится в JavaScript? Сначала мы преобразуем десятичную систему в двоичную,0.1Преобразуется в двоичный код:0.0001100110011001100...(1100 циклов), затем поставить0.2Преобразуется в двоичный код:0.00110011001100...(1100 циклов). Мы обнаружили, что все они являются бесконечно зацикленными бинарными. Очевидно, что компьютер не использует свое собственное «бесконечное пространство» для хранения этих бесконечных циклов двоичных чисел. Так что же делать с такими данными?

Как JavaScript хранит двоичные десятичные числа с бесконечным циклом?

Разные языки могут иметь разные стандарты хранения.Числа, используемые в JavaScript, включают целые числа и десятичные дроби.Существует только один тип.Number, его реализация следуетIEEE 754Стандартный, использующий 64-битную фиксированную длину для представления, то есть стандартные числа с плавающей запятой двойной точности (связанные с числами с плавающей запятой 32-битной одинарной точности), конкретный метод хранения чисел с плавающей запятой двойной точности не будет повторяется здесь (см. следующие главы. подробное описание), нам просто нужно знать, что в двоичной экспоненциальной записи дробная часть числа с плавающей запятой двойной точности может быть зарезервирована не более чем 52 битами (например,1.xxx...*2^n,здесьxЗарезервировано до 52 цифр) плюс предыдущая 1, фактически зарезервировано 53 значащих цифры, а остальные округляются, следуя «0 округлением до 1», затем0.1После округления бинарника получается:

0.00011001100110011001100110011001100110011001100110011010

Точно так же мы получаем0.2Усеченное двоичное представление:

0.0011001100110011001100110011001100110011001100110011010

Добавление двух дает:

0.00011001100110011001100110011001100110011001100110011010 +
0.0011001100110011001100110011001100110011001100110011010 =
0.0100110011001100110011001100110011001100110011001100111

Мы преобразуем результат до десятичных данных в соответствии с формулой или инструментом:

Вы можете видеть, что результат точно такой:0.30000000000000004.

Примечание: десятичные числа в большинстве языков, включая Java, Ruby и Python, по умолчанию соответствуют числам с плавающей запятой, совместимым с IEEE 754. Проблемы с плавающей запятой в этой статье также существуют.

Как хранятся числа с плавающей запятой

В компьютерах представление с плавающей запятой разделено на три части, как показано на рисунке выше:

  • Первая часть (синяя) используется для хранения бита знака (sign), который используется для различения положительных и отрицательных чисел, 0 для положительных чисел.
  • Вторая часть (зеленая) используется для хранения показателя степени
  • Третья часть (красная) предназначена для хранения дробей.

Числа двойной точности с плавающей запятой занимают в общей сложности 64 бита:

  • Знаковый бит (знак) занимает 1 бит
  • Показатель степени занимает 11 бит
  • Дробь занимает 52 бита

Бит знака, бит экспоненты и десятичный бит здесь связаны с экспоненциальной записью. мы начинаем с78.735НапримерПрошлой1.001110011*2^6Это научная запись.Это действительное число получается путем умножения целого числа или числа с фиксированной точкой (т. е. мантиссы) на целую степень основного числа (обычно 2 в компьютерах), что называетсячисло с плавающей запятой. Тогда мы могли бы с тем же успехом занять правильное место в соответствии с этим правилом и положить78.735Преобразованный в представление с двойной точностью, бит знака и десятичный разряд могут быть четко видны, только часть экспоненты должна быть6Преобразование бинарное110В конечном итоге это:

0(sign) 00000000110(exponent) 00111001 10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

(Этот результат на самом деле неверен, в частности, почему он неверен, продолжайте читать ниже)

Давайте посмотрим на вышеупомянутое в соответствии со спецификацией двойной точности.0.1Как именно он хранится, мы знаем, что его двоичный файл:

0.00011001100110011001100110011001100110011001100110011001 10011...

В переводе на научную нотацию это:

1.1001100110011001100110011001100110011001100110011001*2^-4

то есть0.1из:

  • Знаковый бит:0
  • Десятичные разряды:1001100110011001100110011001100110011001100110011001
  • Биты экспоненты:-4

Я ошеломлен здесь.-4Как преобразовать в двоичный файл?Хотя спецификация двойной точности с плавающей запятой определяет знаковый бит, этот знаковый бит представляет собой положительное и отрицательное значение всех данных, а не положительное и отрицательное значение показателя степени.Необходимо ли зарезервировать специальное хранилище для положительного и отрицательного показателя степени? ? ответ отрицательный.

Как хранить отрицательные показатели?

Чтобы уменьшить ненужные проблемы, IEEE предусматривает смещение.Для чего это смещение?Для экспоненциальной части это смещение добавляется каждый раз, чтобы сохранить, поэтому, даже если экспонента отрицательная, то добавьте Вышеупомянутое смещение также становится положительным числом . Чтобы все отрицательные показатели плюс это смещение стали положительными, это смещение также устанавливается регулярно. Взяв в качестве примера двойную точность, мы знаем, что ее экспоненциальная часть составляет 11 двоичных разрядов, тогда диапазон данных, которые могут быть представлены, равен0~2047, правила IEEE1023Смещение для двойной точности.

  1. Когда биты экспоненты не все 0 или все 1 (нормализованное значение), IEEE предусматривает, что формула для вычисления кода экспонентыe-Bias. В это время минимальное значение e равно 1, тогда1-1023= -1022, максимальное значение e равно2046,но2046-1023=1023, видно, что диапазон значений в данном случае составляет-1022~1013.
  2. Когда все биты экспоненты равны 0 (денормализованное значение), IEEE предусматривает, что формула расчета кода экспоненты1-Bias,Прямо сейчас1-1023= -1022.
  3. Когда все биты экспоненты равны 1 (специальное значение), IEEE предусматривает, что это число с плавающей запятой может использоваться для представления 3 специальных значений, а именно положительной бесконечности, отрицательной бесконечности,NaN(not a number). В частности, когда десятичный разряд не равен 0, это означает NaN; когда десятичный разряд равен 0, когда бит знака s=0 означает положительную бесконечность, а когда s=1 означает отрицательную бесконечность.

Посмотрим на этот раз78.735В итоге как преобразовать, часть индекса нужно добавить со смещением при сохранении6+1023то есть1029, преобразованный в двоичный файл:10000000101,так78.735Правильный способ хранения:

0 10000000101 00111001 10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

Аналогично, знаете ли вы0.1Как насчет формы хранения с плавающей запятой двойной точности?

диапазон значений с плавающей запятой

Если вы внимательно прочитаете это, вы сможете рассчитать диапазон значений, которые может представлять JavaScript. Максимальное значение e равно 1023.1.111..(52位)..11*2^1023Преобразуется в обычный двоичный файл:

1 111..(52位)..11 000..(971位)..00

Преобразование двоичного в десятичное:Мы обнаружим, что это значение иNumber.MAX_VALUEодинаковое значение, оба1.7976931348623157e+308. Но на самом деле это значение не самое большое, например, если мы продолжим складывать какие-то числа на основе этого значения, то обнаружим, что оно не возвращается.Infinity.такNumber.MAX_VALUEа такжеInfinityМежду ними еще много чисел.Согласно спецификации IEEE, мы можем знать эту положительную бесконечность тогда и только тогда, когда часть показателя степени равна 1 (максимальное значение части показателя степени).Math.pow(2,11)-1-1023 == 1024), когда дробная часть равна 0, это:

1.000...*2^1024

такMath.pow(2,1024)положительная бесконечность, то на самом деле наибольшее число, которое может хранить JavaScript, равноMath.pow(2,1024)-1. ноNumber.MAX_VALUEа такжеMath.pow(2,1024)Мы не можем представить промежуточные данные, и точность будет потеряна. Таким же образом можно рассчитать и минимальное количество.

Максимальное безопасное целое число для JavaScript

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

1.11..(52位)*2^52

Преобразуется в десятичное числоMath.pow(2,53)-1,Прямо сейчас9007199254740991.

В JavaScript естьNumber.MAX_SAFE_INTEGERпредставлять наибольшее безопасное целое число

Мы обнаружили, что это то же самое значение, которое мы вывели сами.

Как решить проблему с ошибкой расчета

Рекомендуется здесьNumber-PrecisionБиблиотека размером менее 1К.

Справочная статья:

  1. Поймай хвост данных
  2. Основное различие между типами java с плавающей запятой float и double, каков размер их диапазона десятичной точности? - Ответ босса-шарлатана - Зная