предисловие
Как мы все знаем, 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
Смещение для двойной точности.
- Когда биты экспоненты не все 0 или все 1 (нормализованное значение), IEEE предусматривает, что формула для вычисления кода экспоненты
e-Bias
. В это время минимальное значение e равно 1, тогда1-1023= -1022
, максимальное значение e равно2046
,но2046-1023=1023
, видно, что диапазон значений в данном случае составляет-1022~1013
. - Когда все биты экспоненты равны 0 (денормализованное значение), IEEE предусматривает, что формула расчета кода экспоненты
1-Bias
,Прямо сейчас1-1023= -1022
. - Когда все биты экспоненты равны 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К.
Справочная статья: