为什么不起作用
1.0 = 2.0
?不是真正的平等类型吗?它给出了错误:
Error: operator and operand don't agree [equality type required] operator domain: ''Z * ''Z operand: real * real in expression: 1.0 = 2.0
为什么模式中的实数不能像这样工作?
fun fact 0.0 = 1.0 | fact x = x * fact (x - 1.0)
它给出了错误:
Error: syntax error: inserting EQUALOP
1 回答
为什么不起作用
1.0 = 2.0
?不是真正的平等类型吗?
不,类型变量''Z
表明 的操作数=
必须具有相等类型。
为什么模式中的实数不起作用[...]?
模式匹配隐含地依赖于相等性测试。神秘的错误消息syntax error: inserting EQUALOP
表明 SML/NJ 解析器不允许在预期模式的地方使用浮点文字,因此阻止了程序员接收更有意义的类型错误。
详细说明,
来自http://www.smlnj.org/doc/FAQ/faq.txt:
问:real 是相等类型吗?
答:它在 SML '90 和 SML/NJ 0.93 中,但不在 SML '97 和 SML/NJ 110 中。因此
1.0 = 1.0
会导致类型错误,因为“=”需要具有相等类型的参数。此外,真正的文字不能在模式中使用。
来自http://mlton.org/PolymorphicEquality:
一种无法比拟的地型是真实的。所以,
13.0 = 14.0
类型不正确。可以Real.==
用来比较实数是否相等,但要注意这与多态相等具有不同的代数属性。
例如,Real.== (0.1 + 0.2, 0.3)
是false
。
来自http://sml-family.org/Basis/real.html:
决定 real 是否应该是一个相等类型,如果是,相等意味着什么,也是有问题的。IEEE 指定在比较中忽略零符号,并且如果任一参数为 NaN,则相等性评估为 false。
这些限制令 SML 程序员感到不安。前者意味着 0 = ~0 为真,而 r/0 = r/~0 为假。后者意味着诸如 r = r 是错误的异常,或者对于参考单元 rr,我们可以有 rr = rr 但没有 !rr = !rr。我们接受了零的无符号比较,但认为应该保留等价、结构等价和 <> 等价而不是 o = 的反身性质。
简短版本:不要使用相等来比较实数。执行epsilon 测试。我建议阅读http://floating-point-gui.de/errors/comparison上的文章。总之:
不要检查实数是否相同,但差异是否很小。
与差异 ( delta ) 进行比较的误差范围通常称为epsilon。
不要将差异与固定的epsilon进行比较:
fun nearlyEqual (a, b, eps) = Real.abs (a-b) < eps
不要只比较与epsilon的相对差异:
fun nearlyEqual (a, b, eps) = abs ((a-b)/b) < eps
注意边缘情况:
当
b = 0.0
它升高时Div
。(切换a
并b
提供对称的边缘情况。)当
a
和b
位于零的对边时,false
即使它们是可能的最小非零数,它也会返回。结果不可交换。在某些情况下,
nearlyEqual (a, b, eps)
不会给出与 相同的结果nearlyEqual (b, a, eps)
。
该指南提供了一个通用的解决方案;翻译成标准 ML 这看起来像:
fun nearlyEqual (a, b, eps) =
let val absA = Real.abs a
val absB = Real.abs b
val diff = Real.abs (a - b)
in Real.== (a, b) orelse
( if Real.== (a, 0.0) orelse
Real.== (b, 0.0) orelse
diff < Real.minNormalPos
then diff < eps * Real.minNormalPos
else diff / Real.min (absA + absB, Real.maxFinite) < eps )
end
它继续警告一些边缘情况:
在某些情况下,上面的方法仍然会产生意想不到的结果(特别是,当一个值接近零时比恰好为零时更严格),并且开发它通过的一些测试可能指定了不合适的行为对于某些应用程序。在使用它之前,请确保它适合您的应用程序!