Haskell范围和浮动

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标准定义了它的语义如下:

对于类型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实例的定义不同:

对于FloatDouble,系列的语义enumFromInt上面的规则给出,除了当元素变得大于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)

  • 可能的目的是使诸如"[0.0,0.1 .. 1.0]"之类的东西或多或少地像天真地期望不知道浮点不精确一样. (11认同)
  • 这是Haskell规范中的一个令人讨厌的错误.它试图隐藏浮点的固有问题,但这只是意味着问题出现在其他地方. (8认同)
  • @Henning:我认为如果假设在常见用例中,`e3`将在序列`[e1,e1 + i ..]`中,忽略不准确性,这是有道理的.在OP的例子中,`e3`介于两个值之间,这在某种意义上是该假设的最坏情况. (6认同)
  • @leftaroundabout我知道浮点是如何工作的,试图解决问题只会给我带来更多问题.我希望`enumFromThenTo`继续添加增量和元素,只要它们<=上限.将元素置于上限之上是非常令人困惑的. (5认同)
  • 我还要争辩说,任何不了解浮点不精确问题的人都应该*不使用浮点值*.通常,使用性能不是主要问题的任意精度定理,并学习如何在适当的位置正确使用浮动. (4认同)
  • @augustss:好吧,也许您应该提供一些应用程序示例,其中直观的定义实际上会产生有用的结果.据我所知,所有可能超出最后一个值(无论是否预期)可能是实际问题的应用程序都不是安全使用浮点数; 但是我可能错过了一些东西.对于当前定义直接导致最佳结果而直观定义导致不可预测的统计上显着错误的应用程序,请参阅下面的答案(还有更多示例). (4认同)
  • @leftaroundabout如果你问人们他们从列表中直观地期望什么`[x .. y]`我想你会发现他们希望每个elemenet都是> = x和<= y.容易实现,以前它是浮点数的方式.但后来它被"改进"了.额外的元素也不是"接近"上限; 它可以走半步之遥. (3认同)
  • 同意.在SO问题上经常出现不精确的问题.显然,开发人员教育(无论是专业人士还是业余爱好者)都处于失败状态. (2认同)
  • @leftaroundabout即使它是规范,它也可能是一个bug.这不是以前在Haskell中定义的方式,而早期的方法是正确的.目前的行为非常直观. (2认同)

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 有助于记住这些问题!

  • 在考虑之后,我同意间隔大小.IRC的某个人甚至认为这是一个错误,如果这个条件没有得到满足.起初我认为这很荒谬,但现在我明白了他的观点. (2认同)