【发布时间】:2015-05-05 13:30:39
【问题描述】:
得知 Haskell 将其在数字上的 succ 函数定义为加一时,我有点惊讶:
succ :: a -> a
一个值的继承者。对于数字类型,succ 添加 1。
虽然对于整数值,这似乎是合理的,但存在一些问题:
- 如果您定义一个只能表示偶数/奇数/素数/...数字的数字系统,换句话说,一个专用类型是整数的子集;
- 如果您定义某种表示半数和全数的“定点数”,在这种情况下,不会枚举所有数字;和
- 浮点数的问题最为严重。
首先,这意味着[2.0 :: Float .. 3.0 :: Float](使用:: Float 来确保调用没有歧义)仅包含添加到原始值的整数值,而如果使用此表达式,他/她可能会期望列表将包括两个值之间的所有浮点数;当然,这个论点更多的是关于一个人的喜好。大多数程序员在这方面没有太多问题。
更严重的是,如果使用表达式 [2.2 :: Float .. 4.0 :: Float] 会导致 [2.2,3.2,4.2] 4.2 在这里做什么?
如果使用浮点数,而+1 无法生成不同的数字(因为尾数没有足够的位来表示一个),它将无限循环。例如:
Prelude> [1e37 :: Float .. 1e37 :: Float]
[1.0e37,1.0e37,1.0e37,1.0e37,1.0e37,1^C.0e37,Interrupted.
Prelude> [1e37 :: Float .. 1e37-1 :: Float]
[1.0e37,1.0e37,1.0e37,1.0e37,1.0e37,1^C.0e37,Interrupted.
Prelude> [1e37 :: Float .. 1e37+1 :: Float]
[1.0e37,1.0e37,1.0e37,1.0e37,1.0e37,1^C.0e37,Interrupted.
Prelude> [1e37 :: Float .. pred 1e37 :: Float]
[1.0e37,1.0e37,1.0e37,1.0e37,1.0e37,1^C.0e37,Interrupted.
因此,即使列表应该为空或包含一些元素,也会生成无限数量的值。
部分论点确实有点吹毛求疵,但至少对某些人来说,假设 Haskell 程序员最终会犯错误是合理的。
难道不是更合理的方法是生成下一个可表示的浮点数吗?
以这种方式定义succ 的论据是什么? Float 是 Enum 的实例是否合理?
【问题讨论】:
-
浮点
[x..y]间隔在恕我直言中存在固有缺陷——没有合理的方法来定义它们。列出x和y之间的所有浮点数会很有趣,但在实践中并不是很有用(多久会使用一次?)。正如我们现在所拥有的一步式列表会暴露于舍入错误,如果由于舍入错误,1+1+...恰好是100+epsilon,则会导致[1.0 .. 100.0]以99结尾(它不会,但如果我们替换100有足够大的数字..)。 Haskell 采用的“解决方案”将4.2视为4.0+epsilon,因此将其包含在列表中。 (epsilon~0.5) -
@chi:是的,我认为最后一个方面(
4.2=4+e)是有问题的部分。正如问题中所说,它迭代添加积分并没有那么糟糕。但是人们会期望范围在限制之前(或在)结束。Float可能不是Enum的instance可能会更好......毕竟人们仍然可以用map (\x -> fromIntegral x + 0.2) [0..4]模拟它...... -
@chi:列出一个范围内的所有浮点数非常有用,至少对于 32 位浮点数来说是这样。例如,您可以针对某个范围内的所有浮点数测试函数的正确性。
-
@tmyklebu 好的,但你认为在实践中会如此普遍地使用它,值得一个特殊的语法
[x..y]吗?相反,我可以看到它可以作为库函数使用。 -
@CommuSoft 不要误会我的意思——我可以看到选择
floor(y-x)会导致有用的结果(至少可以保证介于两个极端之间)。 Haskell 委员会粗略地选择了round(y-x),可能担心[1.0 .. x]其中x被评估为10.0有一些非常小的错误应该正好有10元素。
标签: haskell floating-point enumerable