Haskell中的数学背后是1.0999999999999999

foo*_*oty 10 floating-point haskell functional-programming floating-accuracy

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

为什么haskel中会出现以下输出:

[0.1,0.3..1]
[0.1,0.3,0.5,0.7,0.8999999999999999,1.0999999999999999]
Run Code Online (Sandbox Code Playgroud)
  1. 什么是背后的数学1.0999999999999999(我在64位Linux机器上,如果它有用)?
  2. 为什么它0.8999999999999999显然1.0999999999999999超出范围时不会停止?

And*_*ewC 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重复出现在数学上与1.1无法区分,但这里我们有9个有限数,并且严格小于1.1.

浮点数被存储为好像它们是科学记数法,例如4.563347x10 ^ -7,但是二进制,所以像01.1001110101x2 ^ 01101110.

这意味着你的号码只能作为浮点数完全准确地存储,如果你可以通过两个幂的总和来表达它,就像你只能用十进制数来表示你可以表达的是10的幂.

在你的例子0.2是二进制0.001100110011,与永远0011重复和1.1是1.0001100110011再次与0011重复下去.

由于只存储了那些有限部分,当转换回十进制显示给你时,它们会有点出局.通常差异是如此之小,它再次被抛弃,但有时你可以看到它,就像在这里.

这种固有的不精确性就是为什么enumFromThenTo让你去上面的上面的数字-它阻止你太少,因为舍入误差.


Chr*_*lor 9

简单的答案

要理解这种行为,你需要知道的是,表达[a,b..c]会被脱到enumFromThenTo a b c哪里enumFromThenTo是一个方法Enum类.

Haskell的标准说,

对于FloatDouble,系列的语义enumFromInt上面的规则给出,除了当元素变得大于e3 + i?2正增量时i,或者当它们变得小于e3 + i?2负数时,列表终止i.

毕竟,标准是标准.但那不是很令人满意.

走得更远

Double实例Enum的模块中被定义GHC.Float,所以让我们看看在那里.我们发现:

instance Enum Double where
  enumFromThenTo = numericFromThenTo
Run Code Online (Sandbox Code Playgroud)

这并不是非常有用,但快速谷歌搜索显示numericFromThenToGHC.Real中定义,所以让我们去那里:

numericEnumFromThenTo e1 e2 e3 = takeWhile pred (numericEnumFromThen e1 e2)
                                where
                                 mid = (e2 - e1) / 2
                                 pred | e2 >= e1  = (<= e3 + mid)
                                      | otherwise = (>= e3 + mid)
Run Code Online (Sandbox Code Playgroud)

那有点好.如果我们假设一个明智的定义numericEnumFromThen,然后调用

numericEnumFromThenTo 0.1 0.3 1.0
Run Code Online (Sandbox Code Playgroud)

会导致

takeWhile pred [0.1, 0.3, 0.5, 0.7, 0.9, 1.1, 1.3 ...]
Run Code Online (Sandbox Code Playgroud)

既然e2 > e1,定义pred

pred = (<= e3 + mid)
  where
    mid = (e2 - e1) / 2
Run Code Online (Sandbox Code Playgroud)

因此x,只要它们满足,我们就会从这个列表中获取元素(称它们)x <= e3 + mid.让我们问一下GHCi的价值是什么:

>> let (e1, e2, e3) = (0.1, 0.3, 1.0)
>> let mid = (e2 - e1) / 2
>> e3 + mid
1.1
Run Code Online (Sandbox Code Playgroud)

这就是你1.09999...在结果列表中看到的原因.

您看到的原因1.0999...,而不是1.1因为1.1没有二进制精确表示.

推理

为什么标准会规定这种奇怪的行为?那么,考虑一下如果只采用满意的数字会发生什么(<= e3).由于浮点错误或e3不可表示性,可能永远不会出现在生成的数字列表中,这可能意味着无关紧要的表达式

[0.0,0.02 .. 0.1]
Run Code Online (Sandbox Code Playgroud)

会导致

[0.0, 0.02, 0.04, 0.06, 0.08]
Run Code Online (Sandbox Code Playgroud)

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