Почему ghci говорит, что 1.1 + 1.1 + 1.1> 3.3 - это Истина? - PullRequest
8 голосов
/ 11 января 2010

Недавно я проходил урок по Haskell и заметил такое поведение, когда пробовал некоторые простые выражения Haskell в интерактивной оболочке ghci:

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

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

Ответы [ 7 ]

36 голосов
/ 11 января 2010

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

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

Для получения более подробной информации по этой теме читайте Что должен знать каждый учёный-компьютерщик об арифметике с плавающей запятой .

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

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

13 голосов
/ 11 января 2010

Вы можете избежать ошибок с плавающей точкой в ​​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

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

13 голосов
/ 11 января 2010

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

6 голосов
/ 11 января 2010

Это связано с тем, как работают числа с плавающей запятой 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
6 голосов
/ 11 января 2010

Похоже на типичную ошибку с плавающей запятой.

См. Что является простым примером ошибки с плавающей запятой / округления?

5 голосов
/ 11 января 2010

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

1 голос
/ 17 июня 2010

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

...