Почему 0,1 + 0,2 возвращает непредсказуемые результаты с плавающей точкой в ​​JavaScript, а 0,2 + 0,3 - нет? - PullRequest
0 голосов
/ 09 июня 2018
0.1 + 0.2
// => 0.30000000000000004

0.2 + 0.2
// => 0.4

0.3 + 0.2
// => 0.5

Я понимаю, что это связано с плавающей точкой, но что именно здесь происходит?

Согласно комментарию @Eric Postpischil, это не дубликат:

Это только связано с тем, почему «шум» появляется в одном дополнении.Этот спрашивает, почему «шум» появляется в одном дополнении и не появляется в другом.Это не ответили в другом вопросе.Следовательно, это не дубликат.Фактически, причина разницы не в арифметике с плавающей точкой как таковой, а в ECMAScript 2017 7.1.12.1 шаг 5

Ответы [ 2 ]

0 голосов
/ 10 июня 2018

При преобразовании числовых значений в строки в JavaScript по умолчанию используется , чтобы использовать только достаточное количество цифр для уникального различения Number значения . 1 Это означает, что при отображении числакак «0,1», это не означает, что это точно 0,1, просто он ближе к 0,1, чем любое другое числовое значение, поэтому отображение только «0,1» говорит вам, что это уникальное числовое значение, равное 0,1000000000000000055511151231257827021181583404541015625.Мы можем записать это в шестнадцатеричном формате с плавающей точкой как 0x1.999999999999ap-4.(p-4 означает умножение предыдущей шестнадцатеричной цифры на две степени −4, поэтому математики запишут ее как 1.999999999999 16 • 2 −4 .)

Вот значения, которые появляются, когда вы пишете 0.1, 0.2 и 0.3 в исходном коде, и они преобразуются в числовой формат JavaScript:

  • 0.1 → 0x1.999999999999ap-4 = 0.1000000000000000055511151231257827021181583404541015625.
  • 0.2 → 0x1.999999999999ap-3 = 0.200000000000000011102230246251565404236316680908203125. * * * тысяча двадцать одна тысяча двадцать два * .3 → 0x1.3333333333333p-2 = 0.299999999999999988897769753748434595763683319091796875. * 1 023 * * * * 1024 +1025 * Когда мыоцените 0.1 + 0.2, мы добавляем 0x1.999999999999ap-4 и 0x1.999999999999ap-3.Чтобы сделать это вручную, мы можем сначала откорректировать последнее, умножив его значение и (дробную часть) на 2 и вычтя одно из его экспоненты, получив 0x3,3333333333334p-4.(Вы должны сделать эту арифметику в шестнадцатеричном формате. A 16 • 2 = 14 16 , поэтому последняя цифра 4, и переносится 1. Затем 9 16 • 2 = 12 16 , и переносимый 1 делает его 13 16 . Это производит 3 цифры и 1 перенос.) Теперь у нас есть 0x1.999999999999ap-4 и 0x3.3333333333334p-4, и мы можем добавить их.Это производит 4.ccccccccccccep-4.Это точный математический результат, но у него слишком много битов для числового формата.Мы можем иметь только 53 бита в значении.Есть 3 бита в 4 (100 2 ) и 4 бита в каждой из последних 13 цифр, то есть всего 55 бит.Компьютер должен удалить 2 бита и округлить результат.Последняя цифра, E 16 , равна 1110 2 , поэтому 10 бит должны быть ушли.Эти биты составляют ровно половину предыдущего бита, поэтому это связь между округлением в большую или меньшую сторону.Правило разрыва связей говорит о округлении, чтобы последний бит был четным, поэтому мы округлим, чтобы 11 битов стали равными 100. E 16 становится 10 16 , вызывая перенос кследующая цифра.Результат равен 4.cccccccccccd0p-4, что равно 0.3000000000000000444089209850062616169452667236328125.

    Теперь мы можем понять, почему при печати .1 + .2 вместо «0.3» отображается «0.30000000000000004».Для значения числа 0.299999999999999988897769753748434595763683319091796875 JavaScript показывает «0.3», потому что это число ближе к 0.3, чем любое другое число.Он отличается от 0,3 примерно на 1,1 в 17 th цифре после десятичной точки, тогда как результат сложения, который мы имеем, отличается от 0,3 примерно на 4,4 в 17 th .Итак:

    • Исходный код 0.3 производит 0.299999999999999988897769753748434595763683319091796875 и печатается как «0.3».
    • Исходный код 0.1 + 0.2 создает 0.30000000000000004440892098500626360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001061 *

    Теперь рассмотрим 0.2 + 0.2.Результатом этого является 0,40000000000000002220446049250313080847263336181640625.Это число ближе всего к 0,4, поэтому JavaScript печатает его как «0,4».

    Наконец, рассмотрим 0.3 + 0.2.Мы добавляем 0x1.999999999999ap-3 и 0x1.3333333333333p-2.Снова настраиваем второй операнд, получая 0x2.6666666666666p-3.Затем добавление дает 0x4.0000000000000p-3, что составляет 0x1p-1, что составляет ½ или 0,5.Таким образом, он печатается как «0.5».

    Другой способ взглянуть на это:

    • Значения для исходного кода 0.1 и 0.2 - это bнемного выше 0,1 и 0,2, соответственно, и добавление их приводит к значению выше 0,3, с ошибками, которые усиливаются, поэтому общая ошибка была достаточно большой, чтобы оттолкнуть результат от 0,3 достаточно, чтобы JavaScript показал ошибку.
    • При добавлении 0.2 + 0.2 ошибки снова усиливаются.Однако общей ошибки в этом случае недостаточно, чтобы отклонить результат слишком далеко от 0,4, чтобы JavaScript отображал его по-другому.
    • Значение для исходного кода 0.3 чуть меньше 0,3.При добавлении к 0.2, что чуть больше 0,2, ошибки отменяются, в результате получается ровно 0,5.

    Сноска

    1 Это правило исходит из шага5 в пункте 7.1.12.1 Спецификации языка ECMAScript 2017.

0 голосов
/ 10 июня 2018

Это не проблема самого javascript или любого другого языка, а скорее проблема общения между двумя расами, такими как человек и машина, и thinking сила обеих.То, что кажется нам вполне естественным (например, слово: tree - когда мы говорим, что создаем абстрактное представление дерева в нашей голове), совершенно не естественно для компьютера, и единственное, что машина может сделать, чтобы обратиться к слову«Древо» - это хранить его в некотором репрезентативном виде, который легко понять на машине (любым способом, который вам действительно нужен, кто-то много лет назад выбрал двоичный код с таблицей ASCII, и он кажется надежным, хотя и сейчас).Итак, в дальнейшем у машины есть представление слова tree, хранящееся где-то там, скажем, это 00000001, но оно ничего не знает кроме этого, для вас это имеет какой-то смысл, для машины это просто куча нулейи один.Если затем мы скажем, что для каждого слова может быть максимум 7 битов, потому что в противном случае компьютер работает медленно, тогда машина сохранит 0000000 обрезая последний бит, и поэтому она все равно будет понимать слово tree в некотором роде.То же самое относится и к числам, 0.3 естественно для вас, но когда вы видите 10101010001010101010101010111, вы сразу же хотите преобразовать его в десятичную систему, чтобы понять, что оно обозначает, потому что для вас не естественно видеть числа в двоичной системе.И вот здесь главное: преобразование .

И, таким образом, для вас математика выглядит так:

.1 + .2 => .3

Для машины, котораяиспользует двоичную систему выглядит следующим образом:

Число 1/10 может быть выражено как 0,1 в десятичном виде, но это 0,0001100110011001100110011001100110011001100110011001… .. в двоичном.Поскольку для номера в соответствии со стандартом имеется только 53-разрядное пространство, начиная с бита 54, число будет округлено.

x = .1 converted to 00011001...01 and trimmed to 53 bits

y = .2 converted to 00010110...10 and trimmed to 53 bits

z = x + y => 0001100010101... trimmed to 53 bits

result = 0.3000000000000000444089209850062616169452667236328125 converted from z = 0001100010101...

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

Таким образом, реальный вопрос - Why does 0.1 converted to binary and trimmed + 0.2 converted to binary and trimmed return unpredictable float results in JavaScript while 0.2 converted to binary and trimmed + 0.3 converted to binary and trimmed does not?, и ответ таков: из-за математики и количества силы, предоставленной для расчетов, аналогично тому, почему pi + 1дает странный результат, но 2 + 1 не = => вы, вероятно, поместили некоторое представление числа пи, например 3.1415, потому что у вас недостаточно математической силы (или не стоит), чтобы получить точный результат.

читайте больше, здесь можно сделать большую часть математики: https://medium.com/dailyjs/javascripts-number-type-8d59199db1b6

...