Haskell范围和浮动

时间:2011-09-03 00:44:06

标签: haskell floating-point range-notation

为什么浮点数的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"

如果最后一个元素接近上限,我会理解它,但这显然不是一个舍入问题。

2 个答案:

答案 0 :(得分:35)

语法[e1, e2 .. e3]实际上是enumFromThenTo e1 e2 e3的语法糖,它是Enum类型类中的函数。

The Haskell standard定义其语义如下:

  

对于类型IntInteger,枚举函数具有   以下含义:

     
      
  • 序列enumFrom e1是列表[e1,e1 + 1,e1 + 2,…]
  •   
  • 序列enumFromThen e1 e2是列表[e1,e1 + i,e1 + 2i,…],   其中增量ie2 − e1。增量可以是零或   负。如果增量为零,则所有列表元素都是   相同。
  •   
  • 序列enumFromTo e1 e3是列表[e1,e1 + 1,e1 + 2,…e3]。   如果e1 > e3
  • ,该列表为空   
  • 序列enumFromThenTo e1 e2 e3是列表[e1,e1 + i,e1 + 2i,…e3],其中增量ie2 − e1。如果增量是   正数或零,列表在下一个元素结束时终止   大于e3;如果e1 > e3,则列表为空。如果增量是   否定,当下一个元素小于时,列表终止   e3;如果e1 < e3
  • ,则列表为空   

这几乎是您所期望的,但FloatDouble实例的定义不同:

  

对于FloatDoubleenumFrom系列的语义由上面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 的违反直觉的结果有助于记住这些问题!