Почему ошибка с плавающей запятой изменяется в зависимости от положения десятичной дроби? - PullRequest
0 голосов
/ 18 октября 2018

Сегодня я столкнулся с ошибкой с плавающей запятой (подтвержденной в JavaScript и Python), которая кажется странной.

> 15 * 19.22
288.29999999999995

Что действительно странно в этом сценарии, так это то, что эти числа находятся в пределах диапазона чисел, представляемыхплавающая запятая.Мы не имеем дело с очень большими или очень маленькими числами.

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

> 15 * 19.22
288.29999999999995
> 1.5 * 192.2
288.29999999999995
> .15 * 1922.
288.3
> 150 * 1.922
288.3
> 1500 * .1922
288.3
> 15 * 1922 / 100
288.3
> 1.5 * 1.922 * 100
288.3
> 1.5 * .1922 * 1000
288.3
> .15 * .1922 * 10000
288.3

Понятнодолжно быть какое-то промежуточное число, которое нельзя представить с плавающей запятой, но как это возможно?

Существует ли "более безопасный" способ умножения чисел с плавающей запятой, чтобы предотвратить эту проблему?Я подумал, что если бы числа были одного порядка, умножение с плавающей запятой сработало бы наиболее точно, но ясно, что это неверное предположение.

Ответы [ 3 ]

0 голосов
/ 18 октября 2018

Это потому, что представляется двоичный файл с плавающей запятой.Возьмите значения 0,8, 0,4, 0,2 и 0,1, как двойные.Их фактически сохраненные значения:

0.8 --> 0.8000000000000000444089209850062616169452667236328125
0.4 --> 0.40000000000000002220446049250313080847263336181640625
0.2 --> 0.200000000000000011102230246251565404236316680908203125
0.1 --> 0.1000000000000000055511151231257827021181583404541015625

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

0.8 --> 0x1.999999999999ap-1
0.4 --> 0x1.999999999999ap-2
etc...

Таким образом, разница между действительным математическим значением и фактически сохраненным значением находится где-то внутри и под этим последним битом.Этот бит становится меньше, чем меньше показатель степени.И он идет вверх в другую сторону: 1.6 - это 0x1.999999999999ap+0 и т. Д. Чем выше вы идете, тем больше будет значение этой разности из-за этого показателя.Вот почему это называется относительной ошибкой.

И если вы сдвинете точку десятичный , вы фактически также измените показатель двоичный .Не совсем пропорционально, потому что мы имеем дело с различными базами чисел, но в значительной степени «эквивалентно» (если это правильное слово).Чем выше число, тем выше показатель степени и, следовательно, тем выше становится значение разницы между математическим значением и значением с плавающей запятой.

0 голосов
/ 19 октября 2018

Не ответ, а длинный комментарий.

Порядок величины не виноват, так как представление с плавающей точкой нормализует числа в мантиссу между 1 и 2:

  15       = 1.875     x 2^3
  19.22    = 1.20125   x 2^4
 150       = 1.171875  x 2^7
   0.1922  = 1.5376    x 2^-3

и показатели обрабатываются отдельно.

0 голосов
/ 18 октября 2018

Почему ошибка с плавающей запятой изменяется в зависимости от положения десятичной дроби?

Поскольку вы работаете с основанием 10. Двоичная с плавающей запятой двойной точности IEEE-754 работает в двоичной форме(база 2).В этом представлении, например, 1 может быть представлено точно, но 0.1 не может. ¹

Что действительно странно в этом сценарии, так это то, что эти числа находятся в пределах диапазона чисел, представляемыхплавающая запятая.Мы не имеем дело с очень большими или очень маленькими числами.

Как вы можете видеть из моего утверждения выше, даже если перейти к десятым, вы сталкиваетесь с неточными числами, не прибегая к возмутительным значениям (как вы делаете, чтобы добраться до непредставимых целых чисел, таких как 9 007 199 254 740 993).Отсюда и знаменитая 0.1 + 0.2 = 0.30000000000000004 вещь:

console.log(0.1 + 0.2);

Существует ли "более безопасный" способ умножения чисел с плавающей запятой, чтобы предотвратить эту проблему?

Не использовать встроенную функцию с плавающей запятойточка.Вы можете работать только в целых числах (так как они надежны от -9,007,199,254,740,992 до 9,007,199,254,740,992), а затем при выводе вставить соответствующий десятичный знак.Вы можете найти ответы на этот вопрос полезными: Как справиться с точностью чисел с плавающей запятой в JavaScript? .


¹ Вам может быть интересно, почему, если 0.1 не отображается точно, console.log(0.1) выводит "0.1".Это происходит потому, что обычно с плавающей запятой при преобразовании в строку выводится только достаточное количество цифр, чтобы отличить число от ближайшего представимого соседа.В случае 0.1 все, что нужно, это "0.1".Преобразование двоичной плавающей запятой в представимое десятичное число довольно сложно, см. Различные примечания и цитаты в спецификации .: -)

...