【问题标题】:Is there a "normal" EqualQ function in Mathematica?Mathematica 中是否有“正常”的 EqualQ 函数?
【发布时间】:2011-06-26 09:57:01
【问题描述】:

在Equal 的文档页面上我们读到了

机器的近似数字 考虑精度或更高 如果它们最多不同,则相等 最后七个二进制数字(大约 最后两位小数)。

这里是示例(32 位系统;对于 64 位系统,在中间添加更多的零):

In[1]:= 1.0000000000000021 == 1.0000000000000022
1.0000000000000021 === 1.0000000000000022

Out[1]= True

Out[2]= True

我想知道 Mathematica 中是否有一个“正常”模拟 Equal 函数不会丢弃最后 7 个二进制数字?

【问题讨论】:

  • SameQ 可以吗?也许在截断到您想要保留的位数之后。
  • @Simon 试试1.00000000000000000022 === 1.00000000000000000021。你会发现它不正常。 :(
  • 猜测...也许 Mathematica 不认为最后一位数字是默认精度下的有效数字。您可以使用反引号表示精度足够高以使所有数字都有效 - 1.00000000000000000022100===1.00000000000000000021100
  • @Alexey - 这就是为什么我说你必须截断到你想要比较的位数。
  • @Alexey 顺便说一句,硬件浮点会给出不确定的结果,也许这就是=== 掉位的原因——thenumericalalgorithmsgroup.blogspot.com/2011/02/…

标签: wolfram-mathematica


【解决方案1】:

感谢最近由 Oleksandr Rasputinov 在官方新闻组上发布的post,现在我学习了两个未记录的函数,它们控制Equal 和SameQ 的容差:$EqualTolerance 和$SameQTolerance。在 Mathematica 版本 5 和更早的版本中,这些函数存在于 Experimental` 上下文中,并且有据可查:$EqualTolerance、$SameQTolerance。从版本 6 开始,它们是 moved to the Internal` context 并且没有记录,但仍然可以工作,甚至具有内置的诊断消息,当人们尝试为其分配非法值时会出现这些消息:

In[1]:= Internal`$SameQTolerance = a

During evaluation of In[2]:= Internal`$SameQTolerance::tolset: 
Cannot set Internal`$SameQTolerance to a; value must be a real 
number or +/- Infinity.

Out[1]= a

引用 Oleksandr Rasputinov:

Internal`$EqualTolerance ... 需要一个 机器实际值表示 小数位数的公差 应该应用,即 Log[2]/Log[10] 倍数 希望的最低有效位 忽略。

这样,将Internal`$EqualTolerance 设置为零将强制Equal 仅在所有二进制数字中都相同时才认为数字相等(不考虑Precision 之外的数字):

In[2]:= Block[{Internal`$EqualTolerance = 0}, 
           1.0000000000000021 == 1.0000000000000022]
Out[2]= False

In[5]:= Block[{Internal`$EqualTolerance = 0}, 
           1.00000000000000002 == 1.000000000000000029]
        Block[{Internal`$EqualTolerance = 0}, 
           1.000000000000000020 == 1.000000000000000029]
Out[5]= True
Out[6]= False

注意以下情况:

In[3]:= Block[{Internal`$EqualTolerance = 0}, 
           1.0000000000000020 == 1.0000000000000021]
        RealDigits[1.0000000000000020, 2] === RealDigits[1.0000000000000021, 2]
Out[3]= True
Out[4]= True

在这种情况下,两个数字都有MachinePrecision,实际上是

In[5]:= $MachinePrecision
Out[5]= 15.9546

(53*Log[10, 2])。有了这样的精度,这些数字在所有二进制数字中都是相同的:

In[6]:= RealDigits[1.0000000000000020` $MachinePrecision, 2] === 
                   RealDigits[1.0000000000000021` $MachinePrecision, 2]
Out[6]= True

将精度增加到 16 会使它们成为不同的任意精度数:

In[7]:= RealDigits[1.0000000000000020`16, 2] === 
              RealDigits[1.0000000000000021`16, 2]
Out[7]= False

In[8]:= Row@First@RealDigits[1.0000000000000020`16,2]
         Row@First@RealDigits[1.0000000000000021`16,2]
Out[9]= 100000000000000000000000000000000000000000000000010010
Out[10]= 100000000000000000000000000000000000000000000000010011

但不幸的是Equal仍然无法区分它们:

In[11]:= Block[{Internal`$EqualTolerance = 0}, 
 {1.00000000000000002`16 == 1.000000000000000021`16, 
  1.00000000000000002`17 == 1.000000000000000021`17, 
  1.00000000000000002`18 == 1.000000000000000021`18}]
Out[11]= {True, True, False}

这样的案例数不胜数:

In[12]:= Block[{Internal`$EqualTolerance = 0}, 
  Cases[Table[a = SetPrecision[1., n]; 
    b = a + 10^-n; {n, a == b, RealDigits[a, 2] === RealDigits[b, 2], 
     Order[a, b] == 0}, {n, 15, 300}], {_, True, False, _}]] // Length

Out[12]= 192

有趣的是,有时RealDigits 返回相同的数字,而Order 表明表达式的内部表示并不相同:

In[13]:= Block[{Internal`$EqualTolerance = 0}, 
  Cases[Table[a = SetPrecision[1., n]; 
    b = a + 10^-n; {n, a == b, RealDigits[a, 2] === RealDigits[b, 2], 
     Order[a, b] == 0}, {n, 15, 300}], {_, _, True, False}]] // Length

Out[13]= 64

但似乎新的相反情况发生了:

In[14]:= 
Block[{Internal`$EqualTolerance = 0}, 
  Cases[Table[a = SetPrecision[1., n]; 
    b = a + 10^-n; {n, a == b, RealDigits[a, 2] === RealDigits[b, 2], 
     Order[a, b] == 0}, {n, 15, 3000}], {_, _, False, True}]] // Length

Out[14]= 0

【讨论】:

  • 感谢您找到并发布此内容。 +1(为什么这没有任何投票?)
  • @Mr.Wizard 添加了进一步的观察。看来Internal`$EqualTolerance 并不像人们想象的那么可靠……
  • Itai Seggev(Wolfram Research)发布的高度相关的 MathGroups 帖子:groups.google.com/d/msg/comp.soft-sys.math.mathematica/…
  • 谢谢,我去看看。
【解决方案2】:

试试这个:

realEqual[a_, b_] := SameQ @@ RealDigits[{a, b}, 2, Automatic]

基数 2 的选择对于确保您比较内部表示至关重要。

In[54]:= realEqual[1.0000000000000021, 1.0000000000000021]
Out[54]= True

In[55]:= realEqual[1.0000000000000021, 1.0000000000000022]
Out[55]= False

In[56]:= realEqual[
           1.000000000000000000000000000000000000000000000000000000000000000022
         , 1.000000000000000000000000000000000000000000000000000000000000000023
         ]
Out[56]= False

【讨论】:

    【解决方案3】:
    In[12]:= MyEqual[x_, y_] := Order[x, y] == 0
    
    In[13]:= MyEqual[1.0000000000000021, 1.0000000000000022]
    
    Out[13]= False
    
    In[14]:= MyEqual[1.0000000000000021, 1.0000000000000021]
    
    Out[14]= True
    

    这会测试两个对象是否相同,因为 1.0000000000000021 和 1.000000000000002100 的精度不同,它们不会被视为相同。

    【讨论】:

    • Mathematica 中的精度与显示的数字是分开的。例如,1.0116 and 1.0100016 具有相同的精度。
    • @Timo: Precision[1.0000000000000021] 是 MachinePrecision (1.0000000000000021`) 但 Precision[1.000000000000002100] 是 18 (1.000000000000002100`18)。表示确实会影响内部表示。试试 FullForm[] 他们。
    • @Kenny:然而 1.1 和 1.10000 都是 MachinePrecision ;-)。我对 OP 的解释是他想比较数值,而不仅仅是数字的样子(SameQ@@ToString/@{#1,#2}& 就足够了,或者你的 Order[])。
    • @Timo:那是因为 1.1 和 1.100000 的数字不到 16 位,可以用 MachinePrecision (IEEE double) 表示。
    • Order -- 好主意!一个不错的简单内置函数。当然,它不会忽略尾随零,但一般来说,比较具有不同精度的接近数字是一件棘手的事情。它经常需要根据具体应用进行详细的数值分析。如果您想要这种级别的控制,那么可能需要像@Timo 那样的RealDigits 解决方案。但我喜欢 Order 的简单性,让 Mathematica 的订购策略处理棘手的情况。 +1
    【解决方案4】:

    我不知道已经定义的运算符。但是你可以定义例如:

    longEqual[x_, y_] := Block[{$MaxPrecision = 20, $MinPrecision = 20},
                                Equal[x - y, 0.]]  
    

    如:

    longEqual[1.00000000000000223, 1.00000000000000223]
    True
    longEqual[1.00000000000000223, 1.00000000000000222]
    False   
    

    编辑

    如果你想概括任意位数,你可以这样做:

    longEqual[x_, y_] :=
     Block[{
       $MaxPrecision =  Max @@ StringLength /@ ToString /@ {x, y},
       $MinPrecision =  Max @@ StringLength /@ ToString /@ {x, y}},
       Equal[x - y, 0.]]
    

    因此,您在评论中的反例也有效。

    HTH!

    【讨论】:

    • 谢谢。但是添加更多的零总是会破坏这种方法:longEqual[1.\ 0000000000000000000000000000000000000000000000000000000000000000000000\ 0000000000000000000000000023, \ 1.00000000000000000000000000000000000000000000000000000000000000000000\ 000000000000000000000000000022]
    • 效果更好,但是当至少有一个数字以NumberMark结尾时会失败:longEqual[1.0000000000000223`, 1.0000000000000222]
    • @Alexey 如果你想保持精度,你应该单独使用 1.55 and not 1.
    【解决方案5】:

    我提出了一个使用RealDigits 来比较数字的实际数字的策略。唯一棘手的一点是去掉尾随的零。

    trunc = {Drop[First@#, Plus @@ First /@ {-Dimensions@First@#, 
             Last@Position[First@#, n_?(# != 0 &)]}], Last@#} &@ RealDigits@# &;
    exactEqual = SameQ @@ trunc /@ {#1, #2} &;
    
    In[1]  := exactEqual[1.000000000000000000000000000000000000000000000000000111,
                         1.000000000000000000000000000000000000000000000000000111000]
    Out[1] := True
    In[2]  := exactEqual[1.000000000000000000000000000000000000000000000000000111,
                         1.000000000000000000000000000000000000000000000000000112000]
    Out[2] := False
    

    【讨论】:

      【解决方案6】:

      我认为你真的必须指定你想要什么......没有办法比较近似的实数,在任何情况下都能让每个人都满意。

      无论如何,这里还有几个选项:

      In[1]:= realEqual[lhs_,rhs_,tol_:$MachineEpsilon] := 0==Chop[lhs-rhs,tol]
      
      In[2]:= Equal[1.0000000000000021,1.0000000000000021]
              realEqual[1.0000000000000021,1.0000000000000021]
      Out[2]= True
      Out[3]= True
      
      In[4]:= Equal[1.0000000000000022,1.0000000000000021]
              realEqual[1.0000000000000022,1.0000000000000021]
      Out[4]= True
      Out[5]= False
      

      随着这两个数字的精度越来越高,如果您将tol 设置得足够高,它们总是可以区分的。

      请注意,减法是以两个数字中最低的精度进行的。您可以通过执行类似

      之类的操作,使其以更高数字的精度发生(这似乎有点毫无意义)
      maxEqual[lhs_, rhs_] := With[{prec = Max[Precision /@ {lhs, rhs}]}, 
        0 === Chop[SetPrecision[lhs, prec] - SetPrecision[rhs, prec], 10^-prec]]
      

      也许使用最小精度更有意义

      minEqual[lhs_, rhs_] := With[{prec = Min[Precision /@ {lhs, rhs}]}, 
        0 === Chop[SetPrecision[lhs, prec] - SetPrecision[rhs, prec], 10^-prec]]
      

      【讨论】:

        【解决方案7】:

        定义此类函数的另一种方法是使用 SetPrecision:

        MyEqual[a_, b_] := SetPrecision[a, Precision[a] + 3] == SetPrecision[b, Precision[b] + 3]
        

        这似乎适用于所有情况,但我仍然想知道是否有内置函数。对这样一个原始任务使用高级函数是丑陋的......

        【讨论】:

        • 只有在 Precision 与您的号码长度相同时才有效,但通常情况并非如此。 MyEqual[1.1113, 1.111000013] -> True.
        • @Alexey Popkov:我喜欢设置偏离 TRUE 值的可容忍百分比,而不是设置精度。例如,假设我有一个真值xT=245 和一个假值xF=250,但我想设置xT=xF,因为与真值的百分比偏差只有 2%,我想容忍这个在显着性检验中偏差并接受相等性。我有大量的方程要容忍,但我不知道如何为我的方程组设置这个容忍度。你能帮我解决这个问题吗?谢谢。
        • @TugrulTemel 我建议您在dedicated site 上创建一个特定问题,并详细说明您希望达到的目标。
        • @AlexeyPopkov:是的,我现在就这样做。感谢你的及时回复。问候,图格鲁尔
        猜你喜欢
        • 2021-03-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-08-06
        • 1970-01-01
        • 2010-11-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多