【问题标题】:Why can't I compare reals in Standard ML?为什么我不能在标准 ML 中比较实数?
【发布时间】:2017-05-14 16:21:07
【问题描述】:
  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
    

【问题讨论】:

  • 不是将这个问题的答案作为对不同问题的答案的一部分,而是在再次提出问题时更容易链接。
  • Computer Science 站点有 reference-question 标记来解决此类问题。这样的标签在这里有用吗?
  • @AntonTrunov:我不知道。这听起来很有用,但我自己可能不会想出使用它。也许这是一种文化的东西。也许在常规的 StackOverflow 上有等效的东西?
  • 我尝试搜索,但在 SO 上找不到任何内容。参考问题的作用是双重的:(1) 显示问题的质量标准,以及 (2) 解决一些常见/基本问题。
  • @AntonTrunov:我以为我会利用ask a meta question的机会,而且似乎普遍认为这样的标签是元,可能不会增加太多价值,并且不是因此在整个 StackOverflow 中都使用了。

标签: floating-point sml smlnj floating-point-comparison


【解决方案1】:

为什么1.0 = 2.0 不起作用? real 不是相等类型吗?

没有。类型变量''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
    
  • 不要只比较 relativeepsilon 的差异:

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

    • 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

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

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

【讨论】:

    猜你喜欢
    • 2013-01-02
    • 1970-01-01
    • 1970-01-01
    • 2021-05-07
    • 1970-01-01
    • 2021-04-18
    • 2021-01-29
    • 1970-01-01
    • 2019-08-26
    相关资源
    最近更新 更多