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.0999999999999999(我在64位Linux机器上,如果它有用)?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让你去上面的上面的数字-它阻止你太少,因为舍入误差.
要理解这种行为,你需要知道的是,表达[a,b..c]会被脱到enumFromThenTo a b c哪里enumFromThenTo是一个方法Enum类.
在Haskell的标准说,
对于
Float和Double,系列的语义enumFrom由Int上面的规则给出,除了当元素变得大于e3 + i?2正增量时i,或者当它们变得小于e3 + i?2负数时,列表终止i.
毕竟,标准是标准.但那不是很令人满意.
该Double实例Enum的模块中被定义GHC.Float,所以让我们看看在那里.我们发现:
instance Enum Double where
enumFromThenTo = numericFromThenTo
Run Code Online (Sandbox Code Playgroud)
这并不是非常有用,但快速谷歌搜索显示numericFromThenTo在GHC.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,我们确保我们得到了这个(可能是更常见的)用例的预期结果.