Что, 0,3 - 0,2 ≠ 0,1?

Java

Ярлыки: Статьи официального аккаунта


Болезненный урок истории

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

После того, как я реализовал алгоритм, я начал тестировать некоторые значения в качестве входных данных и обнаружил, что некоторые значения могут быть протестированы успешно, а другие нет, что мне делать? Отладка, перед лицом такого большого беспорядка кода, отладка - это катастрофа. Кажется, что отладка заняла целый день и ночь. У меня золотые звезды в глазах и я не могу повернуть шею. Это действительно:Писать код какое-то время круто, крематорий отладки.

В конце концов, я действительно обнаружил волшебное явление (и урок, который я всегда помнил позже):

0.2 - 0.1 == 0.1Результат этого выраженияtrue,но0.3 - 0.2 == 0.1Результат этого выражения оказываетсяfalse, роняю любимого, глазам своим не верю. Позже я проверил книгу и сказал, что числа, представленные числами с плавающей запятой, не точны. Что такое неточный метод? Эта статья будет придираться к вам ~

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

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

9.875 = 8 + 1 + 0.5 + 0.25 + 0.125 
      = 1 × 2³ + 1 × 2⁰ + 1 × 2⁻¹ + 1 × 2⁻² + 1 × 2⁻³ 

То есть, если десятичная дробь9.875Преобразование в двоично-десятичное:1001.111. Чтобы хранить такие двоичные десятичные числа в компьютере, мы единообразно представляем их какa × 2ⁿв научной форме записи, где1≤|a|<2,Например1001.111можно выразить как1.001111 × 2³. Ставим после запятой001111называется мантисса, т.середина3Он называется показателем степени, и, поскольку число имеет положительные и отрицательные точки, для представления десятичной дроби необходимы только следующие части:

  • Раздел символов.

  • часть мантиссы.

  • индексная часть.

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

  • Числа с плавающей запятой одинарной точности (тип float в общих языках программирования):

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

    • Используйте 1 бит для представления части знака, значение 0 представляет положительное число, а значение 1 представляет отрицательное число.

    • Используйте 8 бит для представления экспоненциальной части.

    • Часть мантиссы представлена ​​23 битами.

    Нарисуйте схему следующим образом:

    image_1df37645t1v4mml1eo3t3u1v04m.png-78.2kB

  • Числа с плавающей запятой двойной точности (двойной тип в общих языках программирования):

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

    • Знаковая часть представлена ​​с использованием 1 бита.

    • Экспоненциальная часть представлена ​​11 битами.

    • Используйте 52 бита для представления части мантиссы.

    Схема не нарисована.

Для двоично-десятичного числа, представленного научно-техническим методом, достаточно ли непосредственно заполнить число, соответствующее показателю степени, и число, соответствующее мантиссе, в соответствующую позицию? например, для двоичных десятичных знаков1.001111 × 2³(он же десятичный9.875), если оно представлено числом с плавающей запятой одинарной точности, то должно быть так (чтобы каждую часть выразить понятно, мы разделяем каждую часть пробелом, интервала по сути нет):

0 00000011 00111100000000000000000

в:

  • первый бит0Указывает, что это положительное число.

  • 00000011Индекс индикации3.

  • 00111100000000000000000Указывает мантисса001111.

Ха-ха, правда не так проста, дядя, который разработал числа с плавающей запятой, для какой-то цели немного усложнил (эта сложность для нас, людей). Они рассматривают часть экспоненты как число без знака, и буквальное значение этого числа без знака не является реальным значением экспоненты, но конечное значение экспоненты может быть получено после некоторых мучительных методов вычисления. Они разделили метод хранения чисел с плавающей запятой на три ситуации:

  • Когда биты в экспоненциальной части не равны ни всем 0 (числовым 0), ни всем 1 (255 для одинарной точности, 2047 для двойной точности):

    В это время реальное значение экспоненты равно буквальному значению части экспоненты за вычетом значения смещения.Этот так называемый偏置值в числах с плавающей запятой одинарной точности127, в числах с плавающей запятой двойной точности1023.

    Допустим, мы хотим использовать числа с плавающей запятой одинарной точности для хранения двоичных десятичных дробей.1.001111 × 2³, его показатель3, нам нужно вычесть литерал из экспоненциальной части127значение3, поэтому литерал экспоненциальной части равен130, который выражается в двоичном виде как:10000010. Часть мантиссы001111, поэтому он представляет собой двоично-десятичное число1.001111 × 2³Истинная форма с плавающей запятой одинарной точности:

    0 10000010 00111100000000000000000
    

    Советы: Это для волос? Зачем вычитать так называемое значение смещения из литерала. На самом деле, дядя, который проектировал числа с плавающей запятой, считал это.Для связки битов, представляющих показатель степени, определяется общее количество цифр, которые они могут представлять.Например, показательная часть числа с плавающей запятой одинарной точности занимает 8 байт, то он может представлять в общей сложности 2⁸ числа, то есть 256 чисел, но есть особые случаи использования, когда все биты равны 0 и все 1, поэтому часть экспоненты может представлять до 254 чисел, а диапазон числа, которые могут быть представлены, это -126 ~ 127. Они хотят, чтобы двоичное число с наименьшим литералом в виде беззнакового числа, то есть 00000001, представляло наименьшее из 254 чисел, то есть -126, а затем, по мере увеличения литерала, значение действительного показателя, выражаемое также постепенно, увеличивалось до тех пор, пока не наибольший литерал 11111110 представляет истинное значение показателя степени 127. Таким образом, вам нужно всего лишь вычесть 127 из буквального значения, чтобы добиться этого эффекта. Точно так же для чисел с плавающей запятой двойной точности вам нужно вычесть 1023 из беззнакового литерала в части экспоненты, чтобы получить фактическое значение экспоненты. Как видите, дядя, разработавший числа с плавающей запятой, только что ввел странную концепцию значения смещения, чтобы сделать больше буквальное значение и больше реальное значение экспоненты.

  • Когда биты экспоненциальной части равны 0:

    Это особый случай, когда показатель степени представляет не 0, а представляет 1 минус значение смещения.Для чисел с плавающей запятой одинарной точности показатель степени представляет1 - 127 = -126.

    Однако в предыдущем случае значение индекса не было выражено-126Так ли это, почему это значение индекса должно быть-126Ситуация представлена ​​отдельно? Это должно быть выражено из нашего1.001111 × 2³Говоря об этом двоично-десятичном примере, не забывайте, что то, что мы храним в мантиссе, является частью числа с плавающей запятой.001111, то есть тот, что слева от десятичной точки, автоматически игнорируется1, что экономит 1 бит. Однако, когда часть экспоненты равна 0, часть мантиссы не содержит 1 слева от десятичной дроби, например, существует число с плавающей запятой одинарной точности:

    0 00000000 01010000000000000000000
    

    Двоичная дробь, представленная этим числом с плавающей запятой:0.0101 × 2-¹²⁶.

    Можно видеть, что когда все биты мантиссы числа с плавающей запятой одинарной точности равны 0, это означает, что1 × 2-¹²⁶Малые числа, то есть та часть числа, которая близка к 0.

    Когда все биты мантиссы числа с плавающей запятой равны 0, значение может быть представлено0.0, но из-за наличия знакового бита (то есть первого двоичного бита)+0.0и-0.0точки.

  • Когда биты экспоненциальной части равны 1:

    Далее это можно разделить на два случая:

    • Когда биты части мантиссы равны 0:

      В настоящее время он представляет бесконечное значение.Когда бит знака равен 0, это означает положительную бесконечность, а когда бит знака равен 1, это означает отрицательную бесконечность.

    • Когда биты части мантиссы не все равны 0:

      представляетNaNценность,NaNПолное имя не является числом, то есть не представляет число, что полезно в некоторых случаях.

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

Посмотрите еще раз на арифметику с плавающей запятой

Прочитав формат хранения чисел с плавающей запятой, вернемся и посмотрим на исходное предложение0.2 - 0.1 == 0.1значениеtrue0.3 - 0.2 == 0.1Если значение ложно. Давайте возьмем в качестве примера числа с плавающей запятой одинарной точности, чтобы увидеть, как представлены числа, участвующие в этих выражениях:

  • 0.1:

    десятичный десятичный0.1Его нельзя преобразовать в двоично-десятичное число с мантиссой в пределах 23 цифр, поэтому его можно только округлить, чтобы получить приближение:

    1.10011001100110011001101 × 2⁻⁴
    

    Значение индекса-4, в соответствии с первым случаем, который мы описали выше, литерал экспоненциальной части равен123, выраженное как двоично-десятичное число, равно01111011, поэтому мы можем получить десятичную дробь0.1Соответствующие числа одинарной точности с плавающей запятой:

    0 01111011 10011001100110011001101
    
  • 0.2:

    Его двоично-десятичное приближение:

    1.10011001100110011001101 × 2⁻³
    

    Аналогичным образом можно получить следующие числа одинарной точности с плавающей запятой.

    0 01111100 10011001100110011001101
    
  • 0.3:

    Его двоично-десятичное приближение:

    1.00110011001100110011010 × 2⁻²
    

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

    0 01111101 00110011001100110011010
    

Так:

  • рассчитать0.2 - 0.1значение

    Это эквивалентно вычислению:

    1.10011001100110011001101 × 2⁻³ - 1.10011001100110011001101 × 2⁻⁴
    

    Результат:

    1.10011001100110011001101 × 2⁻⁴
    

    И это значение оказывается десятичной дробью0.1двоичное дробное представление , поэтому0.2 - 0.1 == 0.1Результат этого выраженияtrue.

  • рассчитать0.3 - 0.2значение

    Это эквивалентно вычислению:

    1.00110011001100110011010 × 2⁻² - 1.10011001100110011001101 × 2⁻³
    

    Результат:

    1.10011001100110011001110 × 2⁻⁴
    

    И это значение не является десятичной дробью0.1двоичное дробное представление , поэтому0.3 - 0.2 == 0.1Результат этого выраженияfalse.

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

Не по теме

Написание статей очень утомительно, и иногда вы чувствуете, что чтение идет очень гладко, что на самом деле является результатом бесчисленных правок за ним. Если вы думаете, что это хорошо, пожалуйста, помогите переслать его.Большое спасибо~ Вот мой публичный аккаунт "Мы все маленькие лягушки".