Почему ghci говорит, что 1,1 + 1,1 + 1,1 > 3,3 верно?
-
19-09-2019 - |
Вопрос
Недавно я просматривал учебник по 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
Кто-нибудь знает, почему это так?
Решение
Потому что 1.1
а также 3.3
находятся Номера плавучих точек. Анкет Десятичные фракции, такие как .1 или .3, не совсем репрезентативны в бинарном номере плавающей запятой. .1 означает 1/10. Чтобы представить это в бинарном, где каждая фракционная цифра представляет 1/2не (1/2, 1/4, 1/8 и т. Д.), Вам понадобится бесконечное количество цифр, 0,000110011 ... Повторяя бесконечно.
Это точно та же проблема, что и представление, скажем, 1/3 в базе 10. В базе 10 вам потребуется бесконечное количество цифр, .33333 ... навсегда, чтобы представлять 1/3 точно. Итак, работая в базе 10, вы обычно окружаете, к чему -то .33. Но если вы добавите три копии этого, вы получите 0,99, а не 1.
Для гораздо больше информации по этой теме прочитайте Что каждый компьютерный ученый должен знать о арифметике с плавающей запятой.
Для более точного представления рациональных чисел в Haskell вы всегда можете использовать рациональный тип данных, Ratio
; в сочетании с биньями (произвольно большими целыми числами, Integer
в Хаскелле, в отличие от Int
которые являются фиксированным размером) в качестве типа для числителя и знаменателя, вы можете представлять произвольно точные рациональные числа, но с значительно более медленной скоростью, чем числа плавающей запятой, которые реализованы в оборудовании и оптимизированы для скорости.
Номера с плавающими темпами являются оптимизация для научных и численных вычислений, которые обменяют точность для высокой скорости, что позволяет вам выполнять очень большое количество вычислений в небольшом времени, если вы знаете об округлении и о том, как это влияет на ваши вычисления Анкет
Другие советы
Потому что числа с плавающей запятой не являются точными(Википедия)
Вы можете избежать ошибок с плавающей запятой в 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
Конечно, за повышенную точность вы платите штраф за производительность.
Похоже на типичную проблему ошибки с плавающей запятой.
Видеть Что является простым примером ошибки с плавающей запятой/округление?
Это связано с тем, как работают числа плавающей запятой IEEE.
1.1 представлен как 1.100000000000000001 в плавающей запятой, 3,3 представлен как 3,299999999999998.
Итак, 1.1 + 1,1 + 1.1 на самом деле
1.1000000000000001 + 1.1000000000000001 + 1.1000000000000001 = 3.3000000000000003
Что, как вы можете видеть, на самом деле больше 3,299999999999998.
Обычный обходной путь состоит в том, чтобы не оценивать равенство, либо проверить, находится ли число в целевой +/- небольшой эпсилоне (что определяет точность, которая вам нужна).
Пример: если оба истины, то сумма «равна» 3,3 (в пределах допустимой ошибки).
1.1 + 1.1 + 1.1 < 3.3 + 1e9
1.1 + 1.1 + 1.1 > 3.3 - 1e9
Немного поплавка может быть выражено точно с использованием представления IEEE 754, поэтому они всегда будут немного не снимаются.
В целом, вы не должны сравнивать поплавки по равенству (по причинам, изложенным выше). Единственная причина, по которой я могу придумать, это если вы хотите сказать: «Изменилась ли это значение?» Например, «if (newscore /= oldScore)», затем примите какое -то действие. Это нормально, пока вы не сравниваете результат двух отдельных вычислений, чтобы проверить, являются ли они равными (потому что тогда даже математически, если они есть, они могли бы окружить иначе).