哈斯克尔范围和浮游物
为什么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"
如果最后一个元素接近上限,我会理解,但这显然不是一个四舍五入的问题。
语法[e1, e2 .. e3]
实际上是enumFromThenTo e1 e2 e3
语法糖,这是Enum
types中的一个函数。
Haskell标准定义了它的语义如下:
对于
Int
和Integer
types,枚举函数具有以下含义:
- 序列
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
时列表终止e3 + i∕2
负i
。
我不确定这是什么理由,所以我能给你的唯一答案就是这样,因为它是在标准中定义的。
你可以通过枚举使用整数并转换为Float
来解决这个问题。
Prelude> map fromIntegral [1, 3 .. 10] :: [Float] [1.0,3.0,5.0,7.0,9.0]
好的,@Henning Makholm已经在他的评论中这样说过了,但是他没有解释为什么这是一个更好的解决scheme。
首先要说的是:在处理浮点时,我们必须时刻注意到可能的舍入错误。 当我们写[0.0, 0.1 .. 1.0]
我们必须知道,除了第一个数字之外,所有这些数字都不会在十分之十的位置。 我们需要这种确定性,我们绝对不能使用浮筒。
但是,当然有许多应用程序,我们满意合理的内容,但需要高速。 这就是花车很棒的地方。 这种清单的一个可能的应用是简单的梯形数值积分:
trIntegrate flrs = sum [ fx | x<-[l,(l+s)..r] ] * s - (f(l)+f(r))*s/2
让我们testing一下: trIntegrate ( \x -> exp(x + cos(sqrt(x) - x*x)) ) 1.0 3.0 0.1
=> 25.797334337026466
相比之下, 25.9144的误差不到百分之一。 当然不是确切的,但这是整合方法的内在。
现在假设浮动范围被定义为在跨越右边界时始终终止。 那么,这将是可能的(但我们不能确定它),只有20个值而不是21个计算在总和中,因为x
的最后一个值恰好是3.000000个东西。 我们可以模拟这个
bad_trIntegrate flrs = sum [ fx | x<-[l,(l+s)..(rs)] ] * s - (f(l)+f(r))*s/2
那么我们得到
bad_trIntegrate ( \x -> exp(x + cos(sqrt(x) - x*x)) ) 1.0 3.0 0.1
=> 21.27550564546988
urgh!
这与隐藏浮点的问题无关。 这只是帮助程序员更容易解决这些问题的一种方法。 事实上, [1, 3 .. 10] :: Float
的违反直觉的结果有助于记住这些问题!