2010-01-10 3 views
8

Я шел через учебник Haskell недавно и заметил это поведение при попытке несколько простых выражений Haskell в интерактивной оболочке ghci:Почему ghci говорит, что 1.1 + 1.1 + 1.1> 3.3 верно?

Prelude> 1.1 + 1.1 == 2.2 
True 
Prelude> 1.1 + 1.1 + 1.1 == 3.3 
False 
Prelude> 1.1 + 1.1 + 1.1 > 3.3 
True 
Prelude> 1.1 + 1.1 + 1.1 
3.3000000000000003 

Кто-нибудь знает, почему это так?

+0

Nice javascript float - decimal - бинарный визуализатор: http://babbage.cs.qc.edu/IEEE-754/Decimal.html – Seth

+4

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

ответ

36

Поскольку 1.1 и 3.3 являются floating point numbers. Десятичные дробные числа, такие как .1 или .3, точно не представляются в двоичном числе с плавающей запятой. .1 означает 1/10. Чтобы представить это в двоичном формате, где каждая дробная цифра представляет 1/2 n (1/2, 1/4, 1/8 и т. Д.), Вам понадобится бесконечное число цифр, 0,000110011 ... повторяющееся бесконечно.

Это точно такая же проблема, как представление, например, 1/3 в основании 10. В базе 10 вам понадобится бесконечное количество цифр .33333 ... навсегда, чтобы точно представлять 1/3. Поэтому, работая в базе 10, вы обычно кружась, к чему-то вроде .33. Но если вы добавите три копии этого кода, вы получите .99, а не 1.

Для получения дополнительной информации по этой теме читайте What Every Computer Scientist Should Know About Floating Point Arithmetic.

Для представления рациональных чисел точнее в Haskell, вы всегда можете использовать рациональный тип данных, Ratio; в сочетании с bignums (произвольно большие целые числа, Integer в Haskell, в отличие от Int, которые являются фиксированным размером) в качестве типа для числителя и знаменателя, вы можете представлять произвольно точные рациональные числа, но со значительно меньшей скоростью, чем числа с плавающей запятой, которые реализован в аппаратном обеспечении и оптимизирован для скорости.

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

+0

Спасибо, что указали тип данных «Ratio»! –

+0

Просто каламбур, но 'Ratio' - это конструктор типов.' Rational' - это тип, тип (псевдоним) 'Ratio Integer'. – BMeph

+0

@BMeph: Ах да, правда. Тем временем я стал более знаком с Haskell (так как я задал этот вопрос), и конструкторы типов по сравнению с типами больше не путают меня, когда у них одно и то же имя. –

5

Несколько поплавков могут быть выражены точно, используя IEEE 754 представления, поэтому они всегда будут немного прочь.

13

Поскольку числа с плавающей точкой не точны (wikipedia)

+8

-1 Вводящий в заблуждение, извините. Номера с плавающей точкой IEEE * абсолютно точны *, вам просто нужно следить за ошибками округления с десятичными числами. У вас есть те же проблемы, которые пытаются представить дроби, такие как 1/3 в десятичной форме (0.3333 ...). – Seth

+5

Согласовано. Проблема не в точности, это одно из представлений. Номера, которые могут быть представлены полностью, полностью точны. Те, которые лежат между этими числами, могут быть только приблизительными, и это вопрос. – codekaizen

+16

Итак, что вы, два нитпикера, говорите, числа с плавающей запятой являются только неточными, когда они неточны? Ничего себе, ничего не проходит мимо вас. Если '1.1 * 3! = 3.3' в вашей математической системе, то есть * неточно. – Chuck

6

Это связано с тем, как работают числа с плавающей точкой IEEE.

1,1 представлен как 1.1000000000000001 в плавающей запятой, 3.3 представлен как 3.2999999999999998.

Так 1,1 + 1,1 + 1,1 фактически

1.1000000000000001 + 1.1000000000000001 + 1.1000000000000001 = 3.3000000000000003

Который, как вы можете видеть на самом деле больше, чем 3.2999999999999998.

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

Ех: если оба значения истинны, тогда сумма «равна» 3,3 (в пределах допустимой ошибки).

1.1 + 1.1 + 1.1 < 3.3 + 1e9 
1.1 + 1.1 + 1.1 > 3.3 - 1e9 
+9

Нет, 1.1 не представлен как 1.1000000000000001. Он представлен как 1.0001100110011001100110011001100110011001100110011010 (база 2), который при преобразовании в десятичный округляет до 1.100000000000000001 –

+0

Ahh, правда. – Seth

13

Вы можете избежать ошибок с плавающей точкой в ​​Haskell с использованием рациональных типов:

Prelude Data.Ratio> let a = (11 % 10) + (11 % 10) + (11 % 10) 
Prelude Data.Ratio> a > (33 % 10) 
False 
Prelude Data.Ratio> fromRational a 
3.3 

Конечно, вы платите штраф производительности для повышения точности.

+8

Вы даже можете написать 'let a = 1.1 + 1.1 + 1.1 :: Rational', не заботясь о потере точности (буква буквально' 1.1' на самом деле является аббревиатурой от 'fromRational (11% 10)'). –

+0

На самом деле вы можете просто написать '1.1 + 1.1 + 1.1 <(3.3 :: Rational)', чтобы сделать то, что хочет точно. Это скорее неканоническое, потому что ghci по умолчанию этот расчет плавает, так как вы явно не говорите «1.1 + 1.1 + 1.1 <(3.3 :: Float)». – leftaroundabout

1

В общем, вы не должны сравнивать поплавки за равенство (по причинам, изложенным выше). Единственная причина, по которой я могу думать, это если вы хотите сказать «изменилось ли это значение?». Например, «if (newscore/= oldscore)», затем выполните некоторые действия. Это нормально, если вы не сравниваете результат двух отдельных вычислений, чтобы проверить, являются ли они равными (потому что тогда даже математически, если они есть, они могут округлить в противном случае).

Смежные вопросы