Математика за 1.0999999999999999 в Haskell

Возможный дубликат:
Диапазоны и поплавки Haskell

Почему в haskel возникает следующий вывод:

[0.1,0.3..1]
[0.1,0.3,0.5,0.7,0.8999999999999999,1.0999999999999999]
  • Что такое математика за 1.0999999999999999 (я на 64-битной Linux-машине, если она полезна)?
  • Почему он не останавливается на 0.8999999999999999, когда, очевидно, 1.0999999999999999 выходит за пределы допустимого диапазона?

Ответ 1

Почему перерегулирование?

[0.1,0.3..1] не подходит для enumFromThenTo 0.1 0.3 1.0

В отчете Haskell говорится

Для Float и Double семантика семейства enumFrom определяется правилами для Int выше, за исключением того, что список заканчивается, когда элементы становятся больше e3 + i/2 для положительного приращения я или когда они становятся меньше e3 + i/2 для отрицательных i.

Здесь e3= 1.0 и ваш приращение i= 0.2, поэтому e3 + i∕2= 1.1. Он должен был остановиться только тогда, когда он будет больше.

Вы попросили его остановиться на 1, но он может остановиться только на 0,9 или 1,1. Там ошибка округления (плавающие типы по своей сути неточны), а 1.1 закончилась как 1.09999999999, так как это не больше 1.0 + i/2, это разрешено.

Фактически, даже если он был равен 1.0 + i/2, это разрешалось, так как вы можете проверить, используя точный [0.1,0.3..1]::[Rational] (после импорта Data.Ratio).

Вы можете избежать проблемы, вычислив верхний предел, на который вы нацелились, 0,9 и указав, что: [0.1,0.3..0.9]. Вы не будете страдать от ошибки округления, если ваш прирост невелик, а ваши цифры велики, т.е. Вы работаете за пределами точности Double для больших чисел.

Почему неточность?

1.09 повторяемость математически неотличима от 1.1, но здесь мы имеем конечное число 9s и строго меньше 1,1.

Числа с плавающей запятой сохраняются, как если бы они находились в научной нотации, например, 4.563347x10 ^ -7, но в двоичном формате, например 01.1001110101x2 ^ 01101110.

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

0,2 в вашем примере - 0,001100110011 в двоичном формате, а 0011 повторяется навсегда, а 1.1 - 1.0001100110011 снова с повторением на 0011 0011.

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

Эта неотъемлемая погрешность заключается в том, почему enumFromThenTo позволяет перейти к верхнему числу - это мешает вам иметь слишком мало из-за ошибок округления.

Ответ 2

Простой ответ

Чтобы понять это поведение, вам нужно знать, что выражение [a,b..c] будет удалено в enumFromThenTo a b c, где enumFromThenTo - метод класса Enum.

стандарт Haskell говорит, что

Для Float и Double семантика семейства enumFrom задается правилами для Int выше, за исключением того, что список заканчивается, когда элементы становятся больше e3 + i∕2 для положительного приращения i, или когда они становятся меньше e3 + i∕2 для отрицательных i.

Стандарты - это стандарты, в конце концов. Но это не очень удовлетворительно.

Переход глубже

Экземпляр Double Enum определяется в модуле GHC.Float, поэтому давайте посмотрим там. Мы находим:

instance Enum Double where
  enumFromThenTo = numericFromThenTo

Это не невероятно полезно, но быстрый поиск в Google показывает, что numericFromThenTo определяется в GHC.Real, поэтому отпустите:

numericEnumFromThenTo e1 e2 e3 = takeWhile pred (numericEnumFromThen e1 e2)
                                where
                                 mid = (e2 - e1) / 2
                                 pred | e2 >= e1  = (<= e3 + mid)
                                      | otherwise = (>= e3 + mid)

Это немного лучше. Если принять разумное определение numericEnumFromThen, то вызов

numericEnumFromThenTo 0.1 0.3 1.0

приведет к

takeWhile pred [0.1, 0.3, 0.5, 0.7, 0.9, 1.1, 1.3 ...]

Так как e2 > e1, определение pred есть

pred = (<= e3 + mid)
  where
    mid = (e2 - e1) / 2

Поэтому мы будем брать элементы (называть их x) из этого списка, если они удовлетворяют x <= e3 + mid. Позвольте спросить GHCi, что это за значение:

>> let (e1, e2, e3) = (0.1, 0.3, 1.0)
>> let mid = (e2 - e1) / 2
>> e3 + mid
1.1

Вот почему вы видите 1.09999... в списке результатов.

Причина, по которой вы видите 1.0999... вместо 1.1, состоит в том, что 1.1 не представляется точно в двоичном виде.

Обоснование

Почему стандарт должен предписывать такое причудливое поведение? Хорошо, подумайте, что может произойти, если вы только взяли числа, которые удовлетворяли (<= e3). Из-за ошибки или нерепрезентативности с плавающей запятой, e3 никогда не может появляться в списке сгенерированных чисел вообще, что может означать, что безобидные выражения типа

[0.0,0.02 .. 0.1]

приведет к

[0.0, 0.02, 0.04, 0.06, 0.08]

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