Вы действительно знаете, почему 0,1+0,2 не равно 0,3?

внешний интерфейс JavaScript

предисловие

Я, наконец, пережил самый напряженный сезон открытия бизнеса. Я собрал свои мысли и снова получил некоторые базовые знания. Надеюсь, вы сможете придерживаться этого ~ Давай!

Кратко представлено передОсновные операции сложения, вычитания, умножения и деления целых чисел в двоичном формате, я предлагаю, чтобы те, кто не видел его, сначала кратко посмотрели В этом разделе давайте рассмотрим, как основные операции с числами с плавающей запятой выполняются в двоичном формате, и в то же время давайте разберемся с проблемой потери точности. ~

Откройте консоль Chrome и введите особенно простой ввод следующим образом:

0.1 + 0.2  // 0.30000000000000004 

Я не знаю, если вы удивлены, такой простой расчет, будь то в js или python, не является точным 0,3, почему это?

источник

Чтобы понять эту проблему, сначала нам нужно узнать, как числа с плавающей запятой хранятся в компьютере? Я не знаю, что вы думаете. Короче говоря, моей первой реакцией в начале было предположить 32-битное пространство для хранения. Я мог бы представить его в соответствии с методом хранения целых чисел. Например, биты 1-24 - это целые биты. , а оставшиеся 8 бит представляют собой десятичные дроби. , Это нормально? Конечно, можно, но сначала ответьте на следующий вопрос:

1567556120102.jpg

Представьте, что красная область — это самое большое место для чисел, которые можно поместить. Теперь возникает проблема. Когда мы хотим продолжить прибавление 0, мы обнаруживаем, что не можем их вставить, потому что место ограничено. Что нам следует делать? делать в это время?

22222.jpg

Да, правильно, научная нотация означает, что в процессе обучения, если цифр слишком много, мы обычно используем научную нотацию для ее представления.Преимущество этого в том, что количество записываемых цифр мало, а количество цифр выражается велик, так что вернемся к компьютеру, если 32 бита используются для представления действительных чисел, сколько битов может быть представлено не более? Мощность 2^32 составляет около 4 миллиардов, много ли 4 миллиарда? Но по сравнению с бесконечным набором действительных чисел, каплю в море недостаточно, чтобы увидеть, поэтому разработчик компьютера должен рассмотреть эту проблему: как позволить вычислению записать больше чисел?

Действительно существуют «числа с фиксированной точкой».

Помните, что я сказал выше, 1-24 представляют целые биты, а остальные представляют собой дробные биты? Этот метод хранения называется числом с фиксированной запятой.Если каждые 4 бита от 1 до 24 бит представляют число от 0 до 9, 6 бит могут представлять целую часть, а оставшиеся 2 бита представляют собой дробную часть, поэтому мы можем использовать 32 биты для представления числа от 0 до 9. 100 миллионов действительных чисел, таких как 999999,99, этот способ представления десятичного числа в двоичном виде называетсядвоично-десятичное кодированиеДвоичный кодированный десятичный, например, 8421 ярд, веса слева направо 8, 4, 2, 1 и т. д. Если интересно, можете узнать.

Какие проблемы с «числами с фиксированной точкой»

Числа с фиксированной точкой имеют несколько явных недостатков:

  • Он занимает большое количество цифр, но диапазон чисел, которые могут быть представлены, ограничен;
  • Неспособность одновременно представлять очень большие и очень маленькие числа.

Фактически, фундаментальная причина заключается в том, что "ограничение" этого метода ограничивает его. Есть ли способ сделать числа, которые могут быть представлены 32 битами, более "неограниченными" и более подходящими для наших требований?

Конечно, мудрость предшественников в разработке компьютеров безгранична~

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

Точно так же, как и при использовании научной нотации, предшественники компьютеров также использовали ту же идею при разработке чисел с плавающей запятой.Стандарт IEEE определяет два основных формата чисел с плавающей запятой, один из которых представляет собой 32-битное число с плавающей запятой одинарной точности, а второй другое является 64-битным числом с плавающей запятой.Числа с плавающей запятой двойной точности, то есть два формата данных float или float32 и double или float64, представление двойной точности и одинарной точности аналогично, мы понимать и учиться с одинарной точностью.

123.jpg

Делится на 3 части:

  1. Первая частьзнаковый бит, представленный s, представляющий положительное и отрицательное значение, помните, что в диапазоне чисел с плавающей запятой все числа подписаны;
  2. Вторая частьбит экспоненты, представленный буквой e, представляющей показатель степени, диапазон чисел, представленных 8-битными битами, составляет 0 ~ 255, чтобы одновременно представлять большие числа и десятичные дроби, мы удаляем 0 ~ 255 из головы и хвоста (0, 255 будет использоваться позже) 1~254 Для сопоставления с -126~127, так что наибольшее и наименьшее числа могут быть представлены одновременно;
  3. Третья частьзначащие цифры, представленный f, который представляет допустимое количество цифр;

Комбинируя приведенное выше представление и научное обозначение, наше число с плавающей запятой может быть выражено в виде формулы

(-1)^s * 1.f * 2^e

Обнаружили ли вы какие-либо проблемы после прочтения формулы? Вы обнаружите, что наша формула не может представлять 0. Действительно, это умный дизайн. Мы используем 0 (все 8 битов равны 0) и 255 (все 8 битов равны 1) для представления некоторых специальных значений. Можно считать, что два из это специальные биты флага.Например, когда e и f оба равны 0, мы считаем, что это число с плавающей запятой равно 0. См. следующую таблицу:

f922249a89667c4d10239eb8840dc94c.jpg

Возьмите 0,5 в качестве примера, бит знака s 0,5 равен 0, f также равен 0, e равен -1,

Итак, (-1)^0 * 1,0 * 2^-1 = 0,5

В 32-битном представлении это

s e f
0 0111 1110 0000 ...0
1 человек 8 бит 23 бита 0.5

Благодаря этому представлению можно ясно обнаружить, что диапазон действительных чисел, которые могут быть представлены 32 битами, очень велик, и поскольку положение десятичной точки в действительных числах, созданных таким образом, может быть «плавающим», оно также называемые числами с плавающей запятой.

Здесь мы знаем, как хранятся числа с плавающей запятой, но мы не решили начатую проблему, почему 0.1+0.2!=0.3, прежде всего нам нужно знать, как хранится 0.1:

(-1)^s * 1.f * 2^e = 0.1

решить для е

s=0 f=0 e=Math.log2(0.1) // -3.321928094887362

Можно видеть, что 0,1 не может быть рассчитано как точное число.От 0,1 до 0,9 только 0,5 может вычислить точное значение, а остальные не могут вычислить точное значение, поэтому 0,1 + 0,2 будет результирующей проблемой точности, то есть сказать, представляются ли числа с плавающей запятой или вычисляются, на самом деле это приблизительные расчеты, а приблизительные расчеты обязательно приведут к некоторым проблемам.Например, вы хотите, чтобы банк использовал вычисления с плавающей запятой при сохранении денег и расчете процентов? ? Конечно, я не хочу этого, иначе ничего страшного, если у тебя будет слишком много денег, а если слишком мало, не будет ли это большой потерей~

Плавающие и двоичные

Преобразуйте число с плавающей запятой (0,1001), представленное в двоичном виде, в десятичное представление, поскольку каждая цифра после десятичной точки представляет 2 в степени N, поэтому преобразование в десятичное число:

(1 * 2 ^ -1) + (0 * 2 ^ -2) + (0 * 2 ^ -3) + (1 * 2 ^ -4) = 0.5625

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

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

Например, процесс преобразования десятичной части 9.1 и десятичной части 0.1 в двоичную выглядит следующим образом:

f9213c43f5fa658a2192a68cd26435ae.jpg

Это часть «0011», которая получает бесконечный цикл, целая часть 9, преобразованная в двоичную, равна 1001, поэтому результат равен 1001.000110011...

Сдвинув запятую на 3 знака, результат получения числа с плавающей запятой будет 1.001000110011... * 2 ^ 3

Нахождение нашей формулы выше (-1)^s * 1.f * 2^e наборов формул для получения:

s = 0 f = 00100011001100110011 001(Он автоматически отбрасывается после достижения 23 цифр, потому что только 23 значащие цифры могут быть размещены в самой длинной строке.)

Регулированный бит 3, а диапазон нашего E составляет 1-254. Положительные и отрицательные числа разделены на половину, поэтому 127 представляет 0,47, добавить 3, и результат составляет 130. Результат преобразования 130 в двоичный : 1000 0010, поэтому мы получаем E = 1000 0010, результат выглядит следующим образом:

9ace5a7404d1790b03d07bd1b3cb5a27.jpeg

Таким образом, окончательный результат двоичного представления: 0100 0001 0001 0001 1001 1001 1001 1001

Если мы затем преобразуем это представление с плавающей запятой в десятичное, фактическое точное значение, которое мы получим, равно 9,09999942779541015625. Я считаю, что вы не должны чувствовать себя странно сейчас.

Будьте осторожны со своим «депозитом»

Прежде всего, давайте рассмотрим процесс сложения чисел с плавающей запятой.Возьмем для расчета 0,5 + 0,125.Во-первых, результат применения формулы к 0,5:

s = 0 значащий бит 1.f = 1,0000... e = -1;

0,125 означает:

s = 0 значащих битов 1.f = 1,0000... e = -3;

Затем формула вычисления состоит в том, чтобы сначала выровнять биты экспоненты (от малого к большому, здесь e должно быть унифицировано до -1), а затем добавить бит знака и эффективный бит побитово, и e поддерживает унифицированный результат, поэтому:

знаковый бит s показатель е Значимый бит 1.f
0.5 0 -1 1.0
0.125 0 -3 1.0
0,125 выровненных битов экспоненты 0 -1 0.01
0.5 + 0.125 0 -1 1.01

Результат: (-1)^0 * 1,25 * 2^-1 = 0,625;

пс: почему 1.25? Хотя мы вычислили 1,01, не забывайте, что вычисление основано на двоичном, а при десятичном вычислении вы должны повернуть его обратно, поэтому 0100000... Не забудьте -N раз, поэтому результат будет 2^- 2 = 0,25 плюс 1 целочисленного бита равно 1,25~

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

В процессе вычисления его нужно сначала выровнять, но длина значащих цифр 23 бита.Если есть большое число и маленькое число, которое нужно добавить, а затем в процессе выравнивания, десятичное число напрямую переполняется часть 0. Если 23 бита недостаточно, будут проблемы. После заполнения некоторые допустимые биты теряются, что приводит к ошибкам в результате. Разница между битами экспоненты двух чисел превышает 23, например 2^ 24 бита (почти 16 миллионов раз), после сложения этих двух чисел результатом является непосредственно большее число, а меньшее число полностью отбрасывается. . .

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

Math.pow(2, 24) + 0.1 // 16777216.1 

Лжец, неужели результат все равно 0.1, не волнуйся, дружище, встроенный Number в js 64-битный, можешь попробовать

Math.pow(2, 50) + 0.1 // 1125899906842624

Десятичных знаков не хватает? [Это явление также называют большими числами, которые съедают десятичные знаки]

Итак, если банк использует 32-разрядный метод подсчета чисел с плавающей запятой IEEE-754 для хранения вкладов, предположим, что вы крупный начальник и у вас на счету 20 миллионов юаней.В это время один из ваших сотрудников назвал вам 1 юань. , Ха-ха, извините, банк потерял его, ваш депозит не изменился! Таким образом, общие банки и компании электронной коммерции будут использовать числа с фиксированной точкой или целые числа для расчета, когда речь идет о деньгах, чтобы избежать проблемы потери точности.Если вы идете в банк, чтобы задействовать базу данных, вы должны быть осторожны. ~

Суммировать

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

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

продлевать

Потеря точности не является неразрешимой.Есть зрелые решения.Я не буду вводить слишком много.Если вам интересно, вы можете изучить:

Алгоритм формулы суммирования

проиллюстрировать:

Большая часть содержания статьи относится к колонке г-на Сюй Вэньхао «Введение в принципы компоновки компьютеров» и добавляет кое-что из моего собственного понимания, чтобы сделать простое резюме.После этого я продолжу делиться своими доходами со временем. на время. Если вы считаете, что это хорошо, нажмите «Мне нравится» ~

ps: Некоторые студенты могут спросить, поскольку только 0,5 можно преобразовать в точное число, почему нет проблем с 0,1+0,1, я не изучал это внимательно, но я думаю, что это потому, что сам расчет представляет собой процесс вычисления приблизительного значений, поэтому необходимо получитьПосле получения результата, если он все еще находится в пределах приблизительного диапазона, будет считаться, что ошибки нет.Если он выходит за этот диапазон, будет считаться, что есть ошибка. Короче говоря, мы можем подтвердить, что число, полученное в процессе вычисления, действительно является приблизительным числом. Это также верно. Это действительно причина потери точности в некоторых вычислениях с плавающей запятой~

Если вы заинтересованы, вы можете перейти кздесьПосмотрите, какие настоящие числа хранятся в компьютере~