可能重复:
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
超出范围时不停止?
可能重复:
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
超出范围时不停止?[0.1,0.3..1]
简称enumFromThenTo 0.1 0.3 1.0
Haskell 报告说
对于 Float 和 Double, enumFrom 系列的语义由上面的 Int 规则给出,除了当元素变得大于 e3 + i∕2对于正增量 i 或当它们变得小于 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 recurring 在数学上与 1.1 没有区别,但这里我们有有限数量的 9,并且严格小于 1.1。
浮点数以科学记数法存储,例如 4.563347x10^-7,但以二进制形式存储,例如 01.1001110101x2^01101110。
这意味着您的数字只能完全准确地存储为浮点数,如果您可以通过求和 2 的幂来表示它,就像您只能用十进制写一个数字,如果您可以通过求和 10 的幂来表示。
在您的示例中,0.2 是二进制的 0.001100110011,0011 永远重复,1.1 又是 1.0001100110011,0011 永远重复。
由于只有有限的一部分会被存储,当转换回十进制显示给你时,它们会有点出。通常差异是如此之小以至于再次被四舍五入,但有时你可以看到它,就像这里一样。
这种固有的不准确性是为什么enumFromThenTo
让你超过最高数字的原因——它阻止你因为四舍五入错误而拥有太少。
要理解这种行为,您需要知道表达式[a,b..c]
将被分解为enumFromThenTo a b c
where enumFromThenTo
isEnum
类的方法。
Haskell标准说
对于
Float
andDouble
,enumFrom
族的语义由上面的规则给出Int
,除了当元素变得大于e3 + i∕2
正增量i
时,或者当它们变得小于e3 + i∕2
负时,列表终止i
。
毕竟,标准就是标准。但这不是很令人满意。
的Double
实例Enum
在模块GHC.Float中定义,让我们看看那里。我们发现:
instance Enum Double where
enumFromThenTo = numericFromThenTo
这并不是非常有帮助,但是快速的谷歌搜索显示GHC.RealnumericFromThenTo
中定义的,所以让我们去那里:
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
,我们确保我们得到这个(可能更常见的)用例的预期结果。