предисловие
Когда-то мы были удивлены, увидев что-то подобное на консоли
0.1 + 0.2 === 0.3
false
И мы также приходим к причине, из-за потери точности. Ниже я возьму в качестве примера самую простую 0.1, чтобы рассказать вам, почему теряется точность и когда она начинает терять.Нет никаких эзотерических формул и никаких непонятных понятий, пока вы знаете базовое преобразование, вы можете понять его.
0,1 в памяти
Одно мы знаем, общие значения в js хранятся в памяти как 64-битные числа с плавающей запятой, то есть эти 64 двоичных числа сопоставляются с конкретным числом, которое основано на стандарте IEEE754. предлагается исходя из того, как эффективно использовать эти 64 двоичных разряда. Все приведенные ниже процессы основаны на этом стандарте, в котором 64 бита разделены на 3 области.
Бит знака области S представлен битом 1 0 означает положительное число 1 означает отрицательное число
Бит индекса области E представлен 11 битами, имеет положительный и отрицательный диапазон, а критическое значение равно 1023. Вы можете увидеть процесс преобразования позже.
Область M мантисса представлена 52 битами
S + E + M точно равно 64 битам Давайте посмотрим, как 0.1 выглядит в памяти, прежде чем мы начнем
let bytes = new Float64Array(1);// 64位浮点数
bytes[0] = 0.1;// 填充0.1进去
let view = new DataView(bytes.buffer);
console.log(view.getUint8(0).toString(2));// 10011010
console.log(view.getUint8(1).toString(2));// 10011001
console.log(view.getUint8(2).toString(2));// 10011001
console.log(view.getUint8(3).toString(2));// 10011001
console.log(view.getUint8(4).toString(2));// 10011001
console.log(view.getUint8(5).toString(2));// 10011001
console.log(view.getUint8(6).toString(2));// 10111001
console.log(view.getUint8(7).toString(2));// 00111111 这里补齐了8位
Вот куча bytes.buffer от имени места в памяти, для облегчения понимания использую DataView беззнаковый 8-битный формат в память считываемых данных, а затем преобразуется в двоичный формат. Так как последовательная память чтения может быть затронута порядком байтов, может быть напечатана в обратном порядке, чтобы получить ваш компьютер Если вы нажмете расположение SEM, то его двоичный файл будет похож на этот дочерний элемент.
s(0)E(01111111011)M(1001100110011001100110011001100110011001100110011010)
Теперь, когда вы знаете, как выглядит 0,1 в памяти, давайте начнем с конкретного процесса преобразования, то есть процесса потери точности.
0.1 Процесс потери точности
- преобразовать в двоичный
Прежде чем конвертировать сначала посмотрите как перевести десятичные дроби в двоичные десятичные.Это тоже очень ответственный шаг для понимания потери точности.Также в интернете много материалов.Я вкратце напишу процесс ниже.
0.1 => 0.2 => 0.4 => 0.8 => 1.6 => 1.2 => 0.4 => 0.8 => 1.6 => 1.2 => 0.4 => 0.8 => 1.6 => 1.2 => 0.4 ..............
То есть дробная часть непрерывно умножается на 2, а значение целой части берется до тех пор, пока дробная часть не станет равной 0. Это тоже следует хорошо понимать.Видно, что это процесс бесконечного цикла, и он выглядит вот так после конвертации.
0.00011001100110011001100110011001100110011001100110011001100110011001.....
Очевидно, невозможно передавать бесконечные числа в конечном пространстве, так что же нам делать?
-
Преобразуется в двоичный экспоненциальный формат
Преобразуется в формат индекса фактически перемещать десятичную точку, так что десятичная точка появляется перед первым значением 1, разных двоичных данных, может быть возможным быть верным справа, соответствует индексу отрицательного диапазона, так что После конверсии сына
1.1001100110011001100110011001100110011001100110011001100110011001..... * 2 ^ -4
-
Извлечение данных и выполнение числового перехвата, что приводит к потере точности
Здесь видно, что данные сдвинуты вправо на 4 бита, и эти данные будут храниться в области экспоненты E. При отсутствии сдвига значение области экспоненты равно 1023. Если сдвинуть несколько бит в влево, добавьте несколько битов и сдвиньте несколько битов вправо. Просто вычтите несколько битов, так что вот
1023 - 4 = 1019
1019 转二进制并补齐11位 01111111011
То есть Е это 01111111011 Поскольку мантисса имеет не более 52 цифр, все 52 цифры после запятой извлекаются в цифры мантиссы.Следует отметить, что, аналогично округлению, если последняя цифра равна 1, будет сгенерирован перенос, и перенос будет сгенерирован здесь.
1001100110011001100110011001100110011001100110011001100110011001.....
1001100110011001100110011001100110011001100110011001 100110011001.....
进位后截取
1001100110011001100110011001100110011001100110011010
То есть М равно 1001100110011001100110011001100110011001100110011010
Здесь из-за потери некоторых данных теряется точность.
Поскольку 0,1 положительно, S равно 0
На этом весь процесс хранения чисел с плавающей запятой в js закончен.Чтобы показать, что я вас не обманываю, вы можете сравнить значения данных, выведенные в первой части. Ниже будет объяснено, как преобразовать обратно в десятичную, между прочим.
Данные, которые теряют точность, преобразуются обратно в десятичный формат.
- Извлечь данные мантиссы
1001100110011001100110011001100110011001100110011010
- Добавлено ранее 1. Вернуться к экспоненциальному формату и извлечь биты экспоненты
1.1001100110011001100110011001100110011001100110011010
01111111011 => 1019
1019 - 1023 = -4
1.1001100110011001100110011001100110011001100110011010 * 2 ^ -4
- сдвиг
0.00011001100110011001100110011001100110011001100110011010
- двоичный в десятичный В Интернете также много информации о преобразовании десятичного числа из двоичного в десятичное, я также кратко представлю этот процесс на примере 0,0111.
0.0111 小数点后一位 0 / 2^1 0
小数点后2位 1 / 2^2 0.25
小数点后3位 1 / 2^3 0.125
小数点后4位 1 / 2^4 0.0625
然后相加 0 + 0.25 + 0.125 + 0.0625 = 0.4375
Преобразовать, как указано выше
0.00011001100110011001100110011001100110011001100110011010 =>
0.100000000000000005551
Последнее выходное значение на самом деле неточности, потому что я использую JS для расчета его. Если у вас есть более точный метод расчета, вы можете помочь мне рассчитать его. Точное число в конце значения должно быть 5. Но попробуйте оценить следующее выражение в консоли
0.1.toPrecision(21)
"0.100000000000000005551"
Это также доказывает правильность описанного выше процесса рассуждений.
Суммировать
Думаю, вы уже знаете, почему теряется точность.Многие говорят, что js — это яма для вычислений с плавающей запятой.На самом деле она просто следует стандарту.Если это яма, то эта яма не только js.