Разработчики, изучавшие интерфейс, столкнутся со странной проблемой 0,1+0,2!=0,3 при разработке проектов. Если рассуждать с точки зрения традиционной логики, это определенно не соответствует нашим математическим нормам. Так почему же эта базовая ошибка операции возникает в JavaScript и каков принцип? В этой статье разберемся в причинах данной проблемы из принципа
Числовая задача JavaScript
Перед тем, как приступить к принципиальному анализу, автор сначала задает три основных вопроса, над которыми каждый может подумать сначала.
Вопрос один:
Как рассчитать числовое значение в спецификации JavaScript, почему возникает NaN и числовое значение NaN
The Number type has exactly 18437736874454810627 values…(为什么是这个数)
Вопрос второй:
Number.MAX_SAFE_INTEGER === 9007199254740991 //为什么是这个数
Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2 //true
Вопрос третий:
0.1 + 0.2 != 0.3 //原因是什么?
двоичный файл в компьютере
Далее введите текст.Каждый, кто изучал основы компьютера, знает, что нижний слой компьютера должен взаимодействовать с данными через двоичный код. Среди них мы должны понять, почему компьютеры используют двоичные файлы для взаимодействия с данными и что такое двоичные файлы.
1. Почему компьютеры используют двоичные файлы для взаимодействия с данными?
В электронных компьютерах, которые мы используем каждый день, цифровые схемы составляют физическую основу нашего компьютера.Эти цифровые схемы можно рассматривать как набор схем вентилей.Теоретической основой схем вентилей являются логические операции. Затем, когда схемы нашего компьютера включены и работают, на каждом выходе есть напряжение. Уровень напряжения преобразуется в двоичный посредством аналого-цифрового преобразования: высокий уровень представлен 1, а низкий уровень представлен 0.
Проще говоря, основная работа компьютера поддерживается схемой, и схема может легко идентифицировать высокое и низкое напряжение, то есть, пока схема может идентифицировать низкое и высокое напряжение, она может представлять «0» и "1".
2. Что такое бинарный
Двоичный — это то же самое, что наш десятичный, десятичный — каждые десять и один, а двоичный — каждые два и один.
Например, если 001 увеличить на 1, это будет 002 в десятичном виде, а в двоичном — 010, потому что 2 из 002 нужно передать одним битом.
Тогда наши обычные расчеты на компьютере выполняются в десятичном формате, поэтому, когда компьютер обрабатывает наши операции, он преобразует десятичные числа в двоичные числа, а затем выполняет двоичное сложение.Результат преобразуется в десятичный, который представлен на нашем экране. Эти преобразования выполняются внутри компьютера, и мы обычно не можем видеть процесс их преобразования. Тогда вы точно поймете проблему 0,1 + 0,2 != 0,3, вы должны преобразовать из десятичной в двоичную, а затем преобразовать из двоичной в десятичную (精度丢失) Есть отношения.
компьютерные десятичные операции
Как видно из вышеизложенного, мы локализовали проблему, не волнуйтесь, мы сначала определяем, как преобразовать двоичное число в десятичное и десятичное в двоичное, а затем анализируем причину потери точности.
Десятичный в двоичный
Десятичное целое в двоичное
Пример: преобразовать десятичное число 21 в двоичное.
Метод: разделить целое число на 2,Обратный остаток
21 / 2 = 10 -- 1 ⬆
10 / 2 = 5 -- 0 ⬆
5 / 2 = 2 -- 1 ⬆
2 / 2 = 1 -- 0 ⬆
1 / 2 = 0 -- 1 ⬆
Двоичный (обратный остаток): 10101
преобразовать десятичную в двоичную
Пример: преобразовать 0,125 в двоичный код.
Метод: умножьте дробную часть на 2, а затем возьмите целую часть, пока дробная часть не станет равной 0. Если дробная часть не всегда может быть равна 0, то используется округление. Если последняя цифра равна 0, то она отбрасывается. Если за ним следует 1, то идем дальше.Чтение должно быть прочитано от предыдущего целого числа до следующего целого числа
0.125 * 2 = 0.25 -- 0 ⬇
0.25 * 2 = 0.5 -- 0 ⬇
0.5 * 2 = 1.0 -- 1 ⬇
Двоичный: 0,001
двоичный в десятичный
Преобразование двоичного числа в десятичное, метод для целой части и дробной части одинаков.
Пример: преобразовать двоичное число 101.101 в десятичное.
Метод: умножьте число в каждом бите двоичного числа на вес, а затем добавьте сумму к десятичному числу.
После преобразования десятичного числа в двоичное на компьютере выполняется двоичное сложение.
Примечание. В компьютерных операцияхТолько дополнение. например 5 - 5 становится 5 + (-5)
В бинарных операциях для предотвращения некорректных операций и переполнения старшего бита. Вводятся понятия исходного кода, обратного кода и дополнительного кода. Из-за ограниченного места концепции исходного кода, обратного кода и дополнительного кода здесь не будут раскрываться, и заинтересованные читатели могут самостоятельно ознакомиться с информацией.
Числовые значения в JavaScript — числа с плавающей запятой IEEE 754
Итак, после основ давайте вернемся к нашему JavaScript. Как мы все знаем, JavaScript имеет только числовой тип Number, а Number использует 64-битную кодировку чисел двойной точности IEEE 754 с плавающей запятой. Итак, в JavaScriptВсе значения представлены числами с плавающей запятой, то что такое стандарт IEEE 754 и как числовое значение согласовывается в JavaScript.
Стандарт IEEE 754, личное понимание, заключается в управлении положением десятичной точки с помощью экспоненциальной записи для представления различных значений.
В Wiki IEEE 754 определяет четыре способа представления значений с плавающей запятой: одинарная точность (32 бита), двойная точность (64 бита), точность порядка расширения (43 бита, редко используется) и степень расширенной двойной точности (79 бит или более). , обычно достигаемый при 80-битном), обычно мы будем использовать только одинарную интенсивность (32 бита), двойную точность (64-битный)
Представление одинарной точности (32-битное)
Представление двойной точности (64-битное)
Из двух приведенных выше рисунков видно, что когда значение представлено стандартом IEEE 754, оно разделено на три части, включая знак, показатель степени и дробь. Понимание этих трех разделов является целью изучения стандарта IEEE 754. Так что же представляют собой эти три раздела? Не волнуйтесь, давайте сначала разберемся, как наши двоичные значения должны быть представлены после стандарта IEEE 754, а затем выучим эти три определения.
В международном стандарте IEEE 754, будь то 32-битная одинарная точность или 64-битная двойная точность, любойдвоичное число с плавающей запятой VВсе это можно представить в виде следующей картинки, взятой из блога учителя Руан Ифэн.
в:
- (-1)^s представляет бит знака, когда s=0, V положительное, когда s=1, V отрицательное.
- М представляет собой значимое число, большее или равное 1 и меньшее 2.
- E в 2^E обозначает бит экспоненты.
Например, преобразование десятичного числа 7 в двоичное равно 111, что эквивалентно 1,11*2^2, тогда s = 0, M = 1,11, E = 2;
Если десятичное -7 в двоичном равно -111, что эквивалентно -1,11*2^2, то s = 1, M = 1,11, E = 2;
На самом деле, s в формуле эквивалентен знаку (биту знака), чтобы определить, является ли значение положительным или отрицательным, M эквивалентен дроби (значащие цифры), а E эквивалентен показателю степени (показатель степени).
В 32-битной одинарной точности знаковый бит — это старший бит, занимающий один бит, следующие 8 бит — показатель степени E, а оставшиеся 23 бита — значащая цифра M.
В 64-битной одинарной точности знаковый бит — это старший бит, занимающий один бит, следующие 11 бит — это показатель степени E, а оставшиеся 52 бита — это мантиссанда M.
Затем мы обсудим далее, как определяются показатель степени E и значимое число M. Как упоминалось ранее, значимое число М больше или равно 1 и меньше 2. На самом деле это легко понять.В нашей научной записи значащие цифры обычно 1 в начале, то есть в виде 1.XXXX, где XXXX - дробная часть, затем в 32-битной точности, значащее число M занимает 23 цифры, то может ли XXXX занимать только 22 бита, из которых 1 зарезервирован для целочисленной части 1. Чтобы обеспечить 32-битную точность для представления более значащих цифр, умные разработчики стандарта решили, что 1 в целой части не занимает одну из значащих цифр M. Так ХХХХ может занимать 23 бита, поэтому при чтении добавить 1 к первой цифре, тогда это значит, что можно сохранить 24 значащие цифры.IEEE 754 предусматривает, что когда M хранится в компьютере, первая цифра этого числа по умолчанию всегда равна 1, поэтому его можно отбросить, и сохраняется только следующая часть xxxxxx.такой же,М с 64-битной точностью также эквивалентно хранению 53 значащих цифр.
Тогда экспонента E более сложная.Поскольку E является целым числом без знака, в 32-битной точности (E занимает 8 бит) диапазон значений, которые могут быть представлены, составляет от 0 до 255, и он может быть представлен в 64-разрядном формате. битовая точность Значение находится в диапазоне от 0 до 2047. Но на самом деле экспоненциальная часть нашего научного обозначения может оказаться отрицательной. Итак, как использовать E для представления отрицательных чисел, вы можете взять промежуточное значение E, левая часть — отрицательная экспонента, а правая — положительная экспонента. тогдаIEEE 754 предусматривает, что реальное значение E (то есть значение, представленное в степени) должно быть вычтено из промежуточного числа, промежуточное число в 32-битной точности равно 127, а промежуточное число в 64-битной точности равно 1023;, Когда вы видите слова, выделенные жирным шрифтом, вы можете понять, что диапазон индекса на самом деле представляет собой -127 ~ 128; Таким образом, мы можем представить с 32-битной точностью из
Пример: преобразование десятичного числа 7 в двоичное равно 111, что эквивалентно 1,11 * 2 ^ 2. В это время E = 2, тогда E в это время фактически уменьшено на промежуточное значение, поэтому реальное значение E равно 2 + 127 = 129, 10000001 в двоичном формате;
В то же время индекс Е также можетСогласно регламенту, он разделен на три ситуации для обсуждения(обсуждается с 32-битной точностью)
-
E не все 0 или не все 1Этот этап представляет собой обычное представление с плавающей запятой.Вычисляя E и затем вычитая 127, показатель степени равен
-
Е все 0Показатель степени E числа с плавающей запятой равен 0-127 = -127, когда показатель степени равен -127,Значимая цифра М больше не плюс первая 1, но возвращается к десятичной дроби 0.xxxxxx. Это делается для представления ±0 и очень маленьких чисел, близких к 0.
-
Е все 1В это время, если все значащие цифры M равны 0, это означает +∞ или -∞, в зависимости от первого бита знака. Но если значащие цифры М не все 0, значит, это не число (NaN)
Назад к JavaScript
В приведенном выше обсуждении мы редко упоминали JavaScript, что кажется не соответствующим теме нашей статьи, но после понимания вышеизложенных принципов ваше понимание чисел в JavaScript сделает качественный скачок.
Следующее содержание поможет вам шаг за шагом решить эти вопросы, поднятые выше:
1. Числовые величины в спецификации JavaScript, почему это число?
Прежде всего, вам нужно понять, что числа в JavaScript представляют собой 64-битные числа с двойной точностью, поэтому существует 2 ^ 64. Как упоминалось выше, когда E равно 1, это означает либо бесконечное число, либо NaN. Следовательно, существует 2^53 вида возможностей, которые не являются числовыми значениями.В то же время +∞, -∞ и NaN определены как числовые значения в JavaScript. Таким образом, общее количество значений JavaScript равно
В то же время мы также можем напрямую вычислить конкретное количество NaN в JavaScript, потому что определение NaN в приведенном выше состоит в том, что когда E равно 1, если значащее число M не равно 0, это означает, что это не так. число. То есть, за исключением случая, когда все значащие цифры M равны 0 (+∞, -∞)
Максимальная целочисленное значение безопасности 2. JavaScript Почему 9007199254740991
Как было сказано выше, значащих цифр 53 (в том числе 1 в 1.xxxx в первой цифре), если после запятой больше 52 цифр, то соблюдается принцип округления от 0 до 1 в двоичном виде, поэтому такое число не При переписке один в один будут ошибки, и потеряется точность. Это не безопасный номер. Таким образом, наибольшее безопасное целочисленное значение в JavaScript равно
3. 0.1 + 0.2 != 0.3?
Этот вопрос может быть наиболее важным вопросом и самым классическим вопросом интервью по JavaScript. Однако, изучив вышеуказанные знания, все поняли причину проблемы (потеря точности), так как именно он теряется?
Во-первых, операция 0,1 + 0,2 — это сложение десятичной дроби Как упоминалось выше, сложение компьютером десятичной дроби на самом деле заключается в том, чтобы сначала преобразовать десятичную дробь в двоичную, а затем выполнить операцию. Затем нам нужно вычислить двоичный код 0,1, двоичный код 0,2 и двоичный код 0,3 для сравнения и проверки.
Согласно приведенному выше методу расчета, мы можем легко сделать вывод, что двоичный код 0,1 представляет собой бесконечный цикл, то есть
0.1D = (-1)^0 * 1.1001..(1001循环13次)1010B * 2^-4
0.2D = (-1)^0 * 1.1001..(1001循环13次)1010B * 2^-3
0.3D = (-1)^0 * 1.0011..(0011循环13次)0011B * 2^-2
Видно, что при преобразовании 0,1 и 0,2 в двоичные числа значащие цифры составляют 52 бита (4 * 12 + 4), потому что при 64-битной точности можно сохранить только 52 значащих цифры.Если нет 52 значащих цифр На самом деле, в 53-м бите 0,1 в двоичном формате изначально равно 1, но после 52-битного ограничения, в соответствии с двоичным выбором , последние пять цифр начинаются с 10011 (№ 53)становится 1010.
Мы можем вручную вычислить двоичный код 0,1 плюс двоичный код 0,2.
Тогда результат сложения, преобразованный в десятичный вид, на самом деле равен 0,300000000000000004, поэтому 0,1 + 0,2 != 0,3.
конец
Начав со странного вопроса, понять, почему происходит такое явление, и принцип внутри, предположительно это упорство программиста, ищущего истину в фактах, и докопавшись до сути, вы получите больше выигрыша. Я верю, что после прочтения статьи у вас будет более глубокое понимание значений JavaScript.