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

Sim*_*ine 5 floating-point sml smlnj floating-point-comparison

  1. 为什么不起作用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
    
    Run Code Online (Sandbox Code Playgroud)
  2. 为什么模式中的实数不会像这样工作?

    fun fact 0.0 = 1.0
      | fact x = x * fact (x - 1.0)
    
    Run Code Online (Sandbox Code Playgroud)

    它给出了错误:

    Error: syntax error: inserting  EQUALOP
    
    Run Code Online (Sandbox Code Playgroud)

Sim*_*ine 5

为什么不起作用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。

这些限制令 S​​ML 程序员感到不安。前者意味着 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
    
    Run Code Online (Sandbox Code Playgroud)
  • 不要只比较与epsilon相对差异:

    fun nearlyEqual (a, b, eps) = abs ((a-b)/b) < eps
    
    Run Code Online (Sandbox Code Playgroud)
  • 注意边缘情况:

    • b = 0.0它升起时Div。(切换ab提供对称边缘情况。)

    • ab位于零的两侧时,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
Run Code Online (Sandbox Code Playgroud)

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

在某些情况下,上述方法仍然会产生意想不到的结果(特别是,当一个值接近零时比完全为零时要严格得多),并且它开发的一些测试可能会指定不合适的行为对于某些应用程序。在使用它之前,请确保它适合您的应用程序!