Mathematica中是否有一个“标准”的EqualQ函数?

10

Equal 的文档页面中我们可以读到:

机器精度或更高的近似数如果它们在最后七个二进制位(大约相当于它们的最后两个小数位)以内不同,那么它们被认为是相等的。

下面是一些例子(32位系统;对于64位系统,在中间添加更多的零):

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

Out[1]= True

Out[2]= True

我想知道在 Mathematica 中是否存在一个“正常”的类似于 Equal 函数的函数,它不会丢失最后7个二进制位?


SameQ可以吗?也许在截断到您想要保留的数字位数后使用。 - Simon
@Simon 请尝试1.00000000000000000022 === 1.00000000000000000021,你会发现它不可以。:( - Alexey Popkov
猜测...也许Mathematica在默认精度下不认为最后一位数字是有效数字。您可以使用反引号符号表示精度足够高,使所有数字都成为有效数字--1.00000000000000000022100 === 1.00000000000000000021100。 - Yaroslav Bulatov
@Alexey - 这就是我说你必须截断到想要比较的数字位数的原因。 - Simon
SameQ 帮助文档指出它会忽略 Real 对象的最后一位二进制数字。 - Yaroslav Bulatov
2
顺便说一句,硬件浮点运算可能会产生非确定性结果,也许这就是为什么 === 会丢失数字的原因--http://thenumericalalgorithmsgroup.blogspot.com/2011/02/wandering-precision.html - Yaroslav Bulatov
7个回答

17

感谢 Oleksandr Rasputinov 在官方新闻组上的最近 帖子,现在我学会了两个控制EqualSameQ的公差的未公开函数:$EqualTolerance$SameQTolerance。在Mathematica版本5及以下,这些函数位于Experimental`上下文中,并且有很好的文档:$EqualTolerance$SameQTolerance。从版本6开始,它们被移动到Internal`上下文并变成未公开,但仍然可以使用,并且当试图为它们分配非法值时,甚至具有内建的诊断信息:

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
1
@Mr.Wizard 增加了进一步的观察。看起来,“Internal`$EqualTolerance”并不像人们期望的那样可靠... - Alexey Popkov
由Itai Seggev(沃尔夫勒姆研究)发布的与数学相关性强的MathGroups帖子:https://groups.google.com/d/msg/comp.soft-sys.math.mathematica/5zszL5urhYA/KK5BdTIC1yYJ - Alexey Popkov
谢谢,我会看一下。 - Mr.Wizard

8

试试这个:

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

选择二进制作为基础是至关重要的,以确保您在比较内部表示时进行比较。
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

6
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和1.0100016具有相同的精度。 - Timo
@Timo:Precision [1.0000000000000021] 是 MachinePrecision(1.0000000000000021),但 Precision [1.000000000000002100] 是 18(1.00000000000000210018)。表示确实会影响内部表示。尝试 FullForm [] 它们。 - kennytm
@Kenny:然而,1.1和1.10000都是MachinePrecision;-)。我对OP的理解是他想比较数值,而不仅仅是数字的外观(SameQ@@ToString/@{#1,#2}&就足够了,或者你的Order[])。 - Timo
@Timo:这是因为1.1和1.100000都少于~16位数字,这可以由MachinePrecision(IEEE双精度)表示。 - kennytm
Order -- 好主意!一个漂亮简单的内置函数。尽管它不会忽略尾随零,但是通常比较具有不同精度的接近数字是一项棘手的任务。这经常需要详细的数值分析,并由特定应用程序提供信息。如果您想要那种级别的控制,则可能需要像@Timo的RealDigits解决方案。但我喜欢Order的简单性,让Mathematica的排序策略处理复杂的情况。+1 - WReach

5
我不知道是否已经有一个定义好的运算符。但是你可以自己定义,例如:
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.]]

为了让您评论中的反例也能起作用。
希望有所帮助!

1
谢谢。但是添加更多的零总是会破坏这种方法:longEqual[1.\ 0000000000000000000000000000000000000000000000000000000000000000000000\ 0000000000000000000000000023, \ 1.00000000000000000000000000000000000000000000000000000000000000000000\ 000000000000000000000000000022] - Alexey Popkov
1
它的效果更好,但是当至少有一个数字以“NumberMark”结尾时会失败:longEqual [1.0000000000000223',1.0000000000000222]。 - Alexey Popkov
@Alexey 如果你想保持精度,你应该使用1.55而不是仅仅使用1. - Dr. belisarius

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

3

我认为你必须明确你想要什么……没有办法比较近似的实数,以满足每个情况下所有人的需求。

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

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]]

2

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

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

这似乎在所有情况下都有效,但我仍然想知道是否有内置函数可用。对于这样一个基本任务来说,使用高级函数是不太好看的...

1
只有当精度与您的数字长度相同时,它才有效,而这往往不是情况。MyEqual [1.1113,1.111000013] - > True。 - Timo
@Alexey Popkov:与其设置精度,我更喜欢设置与真实值的可容忍偏差百分比。例如,假设我有一个xT=245的真实值和一个xF=250的错误值,但我想将xT=xF,因为相对于真实值的百分比偏差仅为2%,我希望容忍这种偏差并接受类似于显著性测试中的相等性。我有很多方程需要容忍,但我不知道如何为我的方程系统设置这个容差水平。你能帮我解决这个问题吗?谢谢。 - Tugrul Temel
@TugrulTemel 我建议您在专门的网站上创建一个具体的问题,详细描述并提供您希望实现的示例。 - Alexey Popkov
@AlexeyPopkov:是的,我现在就会做。感谢您的及时回复。敬礼,Tugrul - Tugrul Temel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接