为什么浮点数的Haskell范围表示法的行为与整数和字符的行为不同?
Prelude> [1, 3 .. 10] :: [Int]
[1,3,5,7,9]
Prelude> [1, 3 .. 10] :: [Float]
[1.0,3.0,5.0,7.0,9.0,11.0]
Prelude> ['a', 'c' .. 'f']
"ace"
如果最后一个元素接近上限,我会理解它,但这显然不是一个舍入问题。
答案 0 :(得分:35)
语法[e1, e2 .. e3]
实际上是enumFromThenTo e1 e2 e3
的语法糖,它是Enum
类型类中的函数。
The Haskell standard定义其语义如下:
对于类型
Int
和Integer
,枚举函数具有 以下含义:
- 序列
enumFrom e1
是列表[e1,e1 + 1,e1 + 2,…]
。- 序列
enumFromThen e1 e2
是列表[e1,e1 + i,e1 + 2i,…]
, 其中增量i
为e2 − e1
。增量可以是零或 负。如果增量为零,则所有列表元素都是 相同。- 序列
,该列表为空enumFromTo e1 e3
是列表[e1,e1 + 1,e1 + 2,…e3]
。 如果e1 > e3
。- 序列
,则列表为空enumFromThenTo e1 e2 e3
是列表[e1,e1 + i,e1 + 2i,…e3]
,其中增量i
为e2 − e1
。如果增量是 正数或零,列表在下一个元素结束时终止 大于e3
;如果e1 > e3
,则列表为空。如果增量是 否定,当下一个元素小于时,列表终止e3
;如果e1 < e3
。
这几乎是您所期望的,但Float
和Double
实例的定义不同:
对于
Float
和Double
,enumFrom
系列的语义由上面Int
的规则给出,但是当元素变得大于列表时,列表终止e3 + i∕2
为正增量i
,或者当它们低于e3 + i∕2
时为负i
。
我不确定这是什么理由,所以我能给你的唯一答案就是这样,因为它是在标准中以这种方式定义的。
您可以通过使用整数枚举并在之后转换为Float
来解决此问题。
Prelude> map fromIntegral [1, 3 .. 10] :: [Float]
[1.0,3.0,5.0,7.0,9.0]
答案 1 :(得分:13)
好的,@Henning Makholm已在评论中说过这一点,但他没有解释为什么这实际上是一个更好的解决方案。
首先要说的是:在处理浮点数时,我们必须始终注意可能的舍入错误。当我们写[0.0, 0.1 .. 1.0]
时,我们必须意识到除了第一个数字之外的所有这些数字都不会在十分之一的确切位置。在我们需要这种确定性的地方,我们一定不能使用浮子。
但是当然有许多应用程序我们满足于合理确定,但需要高速。这就是花车很棒的地方。这种列表的一个可能应用是简单的梯形数值积分:
trIntegrate f l r s = sum [ f x | x<-[l,(l+s)..r] ] * s - (f(l)+f(r))*s/2
让我们测试一下:trIntegrate ( \x -> exp(x + cos(sqrt(x) - x*x)) ) 1.0 3.0 0.1
=&GT; 25.797334337026466
与25.9144相比,误差小于百分之一。当然不完全正确,但这是集成方法所固有的。
现在假设浮点范围被定义为在越过右边界时始终终止。然后,有可能(但我们无法确定它!)在总和中只计算了20个值而不是21个,因为x
的最后一个值恰好是3.000000。我们可以模拟这个
bad_trIntegrate f l r s = sum [ f x | x<-[l,(l+s)..(r-s)] ] * s - (f(l)+f(r))*s/2
然后我们得到
bad_trIntegrate ( \x -> exp(x + cos(sqrt(x) - x*x)) ) 1.0 3.0 0.1
=&GT; 21.27550564546988
urgh!
这与隐藏浮点问题无关。这只是一种帮助程序员更轻松地解决这些问题的方法。事实上,[1, 3 .. 10] :: Float
的违反直觉的结果有助于记住这些问题!