10

可能重复:
Haskell 范围和浮点数

为什么haskel会出现如下输出:

[0.1,0.3..1]
[0.1,0.3,0.5,0.7,0.8999999999999999,1.0999999999999999]
  1. 背后的数学原理是什么1.0999999999999999(如果有用的话,我在 64 位 linux 机器上)?
  2. 0.8999999999999999为什么它在明显1.0999999999999999超出范围时不停止?
4

2 回答 2

19

为什么会超调?

[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让你超过最高数字的原因——它阻止你因为四舍五入错误而拥有太少。

于 2012-11-02T22:14:31.787 回答
9

简单的答案

要理解这种行为,您需要知道表达式[a,b..c]将被分解为enumFromThenTo a b cwhere enumFromThenToisEnum类的方法。

Haskell标准

对于Floatand DoubleenumFrom族的语义由上面的规则给出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.11.1

推理

为什么标准会规定这种奇怪的行为?好吧,考虑一下如果你只取满足的数字会发生什么(<= e3)。由于浮点错误或不可表示性,e3可能永远不会出现在生成的数字列表中,这可能意味着像这样的无害表达式

[0.0,0.02 .. 0.1]

会导致

[0.0, 0.02, 0.04, 0.06, 0.08]

这似乎有点奇怪。由于 中的更正numericFromThenTo,我们确保我们得到这个(可能更常见的)用例的预期结果。

于 2012-11-02T22:13:26.700 回答