使用Haskell范围:为什么在一个范围内映射浮点函数会导致它返回一个额外的元素?

时间:2014-04-07 00:13:17

标签: haskell floating-point range

我知道漂浮物由于其不精确的性质而导致范围内的奇怪行为。 我预计价值不精确的可能性。例如: [0.1,0.3..1]可能会[0.1,0.3,0.5,0.7,0.8999999999999999]代替[0.1,0.3,0.5,0.7,0.9]

然而,除了精度损失之外,我还有一个额外的元素:

ghci> [0.1,0.3..1]
[0.1,0.3,0.5,0.7,0.8999999999999999,1.0999999999999999]

这很奇怪,但解释here。我想这样可以解决这个问题,我想:

ghci> [0.1,0.3..0.99]
[0.1,0.3,0.5,0.7,0.8999999999999999]

但这有点糟糕。也许有一种更清洁的方式。对于这个简单的例子,当然,我可以使用范围[0.1,0.3..0.9],一切都很好。

但是在一个更复杂的例子中,我可能不会很快知道(或者想弄清楚,如果我是懒惰的)我应该使用的确切上限。所以,我只是制作一系列整数然后除以10,对吧?都能跟得上:

ghci> map (/10) [1,3..10]
[0.1,0.3,0.5,0.7,0.9,1.1]

任何浮点函数似乎都会导致此行为:

ghci> map (*1.0) [1,3..10]
[1.0,3.0,5.0,7.0,9.0,11.0]

非浮动函数不是:

ghci> map (*1) [1,3..10]
[1,3,5,7,9]

虽然看起来不太可能,但我认为可能有一些懒惰的评估在起作用,并试图首先强制评估范围:

ghci> let list = [1,3..10] in seq list (map (*1.0) list)
[1.0,3.0,5.0,7.0,9.0,11.0]

显然,使用文字列表而不是范围可以正常工作:

ghci> map (*1.0) [1,3,5,7,9]
[1.0,3.0,5.0,7.0,9.0]

ghci> let list = [1,3,5,7,9] in seq list (map (*1.0) list)
[1.0,3.0,5.0,7.0,9.0]

它不只是映射:

ghci> last [1,3..10]
9

ghci> 1.0 * (last [1,3..10])
11.0

如何将函数应用于范围的结果会影响该范围的实际评估结果?

1 个答案:

答案 0 :(得分:11)

我在写这篇文章时为自己回答了这个问题。

Haskell使用类型推断,因此当它看到一个浮点函数被映射到一个列表上(或者在该列表的一个元素上使用时,就像在我的示例中使用last),它将推断该列表的类型为是浮点数因此评估范围,好像它是[1,3..10] :: [Float]而不是我想要的,[1,3..10] :: [Int]

此时,它使用Float规则进行枚举,如post that I linked to in the question中所述。

可以像这样强制预期的行为:

ghci> map (\x -> (fromIntegral x) / 10) ([1,3..10]::[Int])
[0.1,0.3,0.5,0.7,0.9]

依赖于Haskell的类型推断,我们可以删除::[Int],因为fromIntegral导致我们的lambda表达式具有正确的类型:

ghci> :t (\x -> (fromIntegral x) / 10)
(\x -> (fromIntegral x) / 10)
  :: (Fractional a, Integral a1) => a1 -> a