为什么我不能在标准 ML 中比较实数?

Why can't I compare reals in Standard ML?

  1. 为什么 1.0 = 2.0 不起作用? real不是等式吗?

    它给出错误:

    Error: operator and operand don't agree [equality type required]
      operator domain: ''Z * ''Z
      operand:         real * real
      in expression:
        1.0 = 2.0
    
  2. 为什么形态中的实数不能像这样工作?

    fun fact 0.0 = 1.0
      | fact x = x * fact (x - 1.0)
    

    它给出错误:

    Error: syntax error: inserting  EQUALOP
    

Why doesn't 1.0 = 2.0 work? Isn't real an equality type?

没有。类型变量 ''Z 表示 = 的操作数必须具有相等类型。

Why won't reals in patterns work [...]?

模式匹配隐含地依赖于相等性测试。神秘的错误消息 syntax error: inserting EQUALOP 表示 SML/NJ 解析器不允许在需要模式的地方使用浮点文字,因此程序员无法收到更有意义的类型错误。

详细说明,

来自 http://www.smlnj.org/doc/FAQ/faq.txt:

Q: Is real an equality type?

A: It was in SML '90 and SML/NJ 0.93, but it is not in SML '97 and SML/NJ 110. So 1.0 = 1.0 will cause a type error because "=" demands arguments that have an equality type. Also, real literals cannot be used in patterns.

来自 http://mlton.org/PolymorphicEquality:

The one ground type that can not be compared is real. So, 13.0 = 14.0 is not type correct. One can use Real.== to compare reals for equality, but beware that this has different algebraic properties than polymorphic equality.

例如Real.== (0.1 + 0.2, 0.3)就是false.

来自 http://sml-family.org/Basis/real.html:

Deciding if real should be an equality type, and if so, what should equality mean, was also problematic. IEEE specifies that the sign of zeros be ignored in comparisons, and that equality evaluate to false if either argument is NaN.

These constraints are disturbing to the SML programmer. The former implies that 0 = ~0 is true while r/0 = r/~0 is false. The latter implies such anomalies as r = r is false, or that, for a ref cell rr, we could have rr = rr but not have !rr = !rr. We accepted the unsigned comparison of zeros, but felt that the reflexive property of equality, structural equality, and the equivalence of <> and not o = ought to be preserved.

简短版本:不要使用相等来比较实数。执行 epsilon 测试。我建议阅读 http://floating-point-gui.de/errors/comparison 上的文章。总结:

  • 不检查实数是否相同,但如果差异很小。

  • 与差值(delta)进行比较的误差范围通常称为epsilon.

  • 不要将差异与固定的 epsilon:

    进行比较
    fun nearlyEqual (a, b, eps) = Real.abs (a-b) < eps
    
  • 不要只比较 relativeepsilon:

    的区别
    fun nearlyEqual (a, b, eps) = abs ((a-b)/b) < eps
    
  • 注意边缘情况:

    • b = 0.0 时它加注 Div。 (切换 ab 提供了对称的边缘情况。)

    • ab 位于零的相反侧时 returns 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

它继续警告一些边缘情况:

There are some cases where the method above still produces unexpected results (in particular, it’s much stricter when one value is nearly zero than when it is exactly zero), and some of the tests it was developed to pass probably specify behaviour that is not appropriate for some applications. Before using it, make sure it’s appropriate for your application!