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

时间:2016-12-30 10:51:12

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

  1. 为什么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)
    
  2. 为什么模式中的实物不能像这样工作?

    Error: syntax error: inserting  EQUALOP
    

    它给出错误:

    {{1}}

1 个答案:

答案 0 :(得分:3)

  

为什么1.0 = 2.0不起作用? 真实不是一个相等类型吗?

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

  

为什么模式中的实物不会起作用[...]?

模式匹配隐含地依赖于测试相等性。神秘的错误消息syntax error: inserting EQUALOP表明SML / NJ解析器不允许出现模式的浮点文字,因此阻止程序员接收更有意义的类型错误。

详细说明,

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

  问:真的是一种平等的类型吗?

     

答:它是在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

  

判断真实是否应该是一个平等类型,如果是,那么平等意味着什么,也是有问题的。 IEEE规定在比较中忽略零的符号,如果任一参数为NaN,则该等式求值为false。

     

这些限制令SML程序员感到不安。前者暗示0 =〜0为真,而r / 0 = r / ~0为假。后者意味着r = r为假的这种异常,或者对于ref cell 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时。 (切换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

它继续警告一些边缘案例:

  

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