Подтвердить что ты не робот

Математика с плавающей запятой в разных языках программирования

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

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

В python2/3

.4 + .2
0.6000000000000001
.4 + .1 + .1
0.6

То же самое происходит в Julia 0.3

julia> .4 + .2
0.6000000000000001

julia> .4 + .1 + .1
0.6

и Scala:

scala> 0.4 + 0.2
res0: Double = 0.6000000000000001

scala> 0.4 + 0.1 + 0.1
res1: Double = 0.6

и Haskell:

Prelude> 0.4 + 0.2
0.6000000000000001    
Prelude> 0.4 + 0.1 + 0.1
0.6

но R v3 правильно:

> .4 + .2
[1] 0.6
> .4 + .1 + .1
[1] 0.6
4b9b3361

Ответ 1

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

Однако это не объясняет различия между языками: во всех ваших примерах выполняются одни и те же вычисления и получены точные результаты. Разница заключается в том, что различные языки предпочитают отображать результаты.

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

0.600000000000000088817841970012523233890533447265625

тогда как точное значение второй суммы:

0.59999999999999997779553950749686919152736663818359375

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

Существует много возможных стратегий форматирования, но три наиболее распространенных:

  • Вычислить и отобразить 17 правильно округленных значащих цифр, возможно, зачищая конечные нули, где они появляются. Выход из 17 цифр гарантирует, что отдельные бинарные64-поплавки будут иметь разные представления, так что значение с плавающей запятой можно однозначно восстановить из своего представления; 17 - наименьшее целое с этим свойством. Это стратегия, которую использует, например, Python 2.6.

  • Вычислить и отобразить кратчайшую десятичную строку, которая округляется до заданного значения binary64 в обычном режиме округления до четного округления. Это сложнее реализовать, чем стратегия 1, но сохраняет свойство, что отдельные поплавки имеют различные представления и, как правило, делают для получения удовольствия. Это, по-видимому, стратегия, в которой используются все языки, которые вы тестировали (помимо R).

  • Вычислить и отобразить 15 (или меньше) правильно округленных значащих цифр. Это приводит к скрытию ошибок, связанных с преобразованием десятичных чисел в двоичные числа, что дает иллюзию точной десятичной арифметики. У этого есть недостаток, что отдельные поплавки могут иметь одинаковое представление. Кажется, это то, что делает R. (Спасибо @hadley за указание в комментариях, что существует R настройка, которая управляет количеством цифр, используемых для отображения, по умолчанию используется 7 значащих цифр.)

Ответ 2

Вы должны знать, что 0.6 не может быть точно представлен в плавающей запятой IEEE, и не может 0.4, 0.2 и 0.1. Это связано с тем, что отношение 1/5 является бесконечно повторяющейся фракцией в двоичном формате, так же как отношения, такие как 1/3 и 1/7, находятся в десятичной форме. Поскольку ни одна из ваших исходных констант не является точной, неудивительно, что ваши результаты также не точны. (Примечание: если вы хотите получить лучшую ручку от этого недостатка точности, попробуйте вычесть значение, которое вы ожидаете от вычисленных результатов...)

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

Обычным советом для этой ситуации является чтение "Что каждый компьютерный ученый должен знать о арифметике с плавающей точкой" , Дэвид Голдберг. Суть: плавающая точка не является точной, и наивные предположения о ее поведении могут не поддерживаться.

Ответ 3

Причина в том, что он округляется в конце в соответствии со стандартом IEEE для арифметики с плавающей запятой:

http://en.wikipedia.org/wiki/IEEE_754

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