und*_*gor 42 floating-point haskell 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"
Run Code Online (Sandbox Code Playgroud)
如果最后一个元素接近上限,我会理解它,但这显然不是一个舍入问题.
ham*_*mar 37
语法[e1, e2 .. e3]
实际上是语法糖enumFromThenTo e1 e2 e3
,它是Enum
类型类中的一个函数.
Haskell标准定义了它的语义如下:
对于类型
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]
Run Code Online (Sandbox Code Playgroud)
lef*_*out 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
Run Code Online (Sandbox Code Playgroud)
让我们测试:trIntegrate ( \x -> exp(x + cos(sqrt(x) - x*x)) ) 1.0 3.0 0.1
=> 25.797334337026466
相比25.9144一个的不到百分之一错误.当然不完全正确,但这是集成方法所固有的.
现在假设浮点范围定义为在越过右边界时始终终止.然后,有可能(但我们无法确定它!)在总和中只计算了20个值而不是21个,因为最后一个值x
碰巧是3.000000something.我们可以模拟这个
bad_trIntegrate f l r s = sum [ f x | x<-[l,(l+s)..(r-s)] ] * s - (f(l)+f(r))*s/2
Run Code Online (Sandbox Code Playgroud)
然后我们得到
bad_trIntegrate ( \x -> exp(x + cos(sqrt(x) - x*x)) ) 1.0 3.0 0.1
Run Code Online (Sandbox Code Playgroud)
=> 21.27550564546988
喔!
这与隐藏浮点问题无关.这只是一种帮助程序员更轻松地解决这些问题的方法.事实上,违反直觉的结果[1, 3 .. 10] :: Float
有助于记住这些问题!