DeleteDuplicates和Tally中的不稳定性

Mr.*_*ard 11 precision wolfram-mathematica duplicates

在准备一个答案来计算列表在Mathematica中有多少不同的值时,我遇到了两个不稳定(缺乏一个更好的术语)DeleteDuplicates并且Tally我不明白.

先考虑一下:

a = {2.2000000000000005, 2.2, 2.1999999999999999};

a // InputForm
DeleteDuplicates@a // InputForm
Union@a // InputForm
Tally@a // InputForm
Run Code Online (Sandbox Code Playgroud)
   {2.2000000000000006`, 2.2, 2.1999999999999997`}
   {2.2000000000000006`, 2.2, 2.1999999999999997`}
   {2.1999999999999997`, 2.2, 2.2000000000000006`}
   {{2.2000000000000006`, 3}}

这种行为正如我在每种情况下所预期的那样. Tally补偿微小的数值差异,并将每个元素看作是等价的. Union并将DeleteDuplicates所有元素视为唯一.(据Tally我所知,这种行为没有记录,但我之前已经使用过它.)

现在,考虑一下这个并发症:

a = {11/5, 2.2000000000000005, 2.2, 2.1999999999999997};

a // InputForm
DeleteDuplicates@a // InputForm
Union@a // InputForm
Tally@a // InputForm
Run Code Online (Sandbox Code Playgroud)
   {11/5, 2.2000000000000006, 2.2, 2.1999999999999997}
   {11/5, 2.2000000000000006, 2.2}
   {2.1999999999999997, 2.2, 11/5, 2.2000000000000006}
   {{11/5, 1}, {2.2000000000000006, 1}, {2.2, 2}}

的输出Union是如预期,但无论从效果DeleteDuplicatesTally令人惊讶.

  • 为什么DeleteDuplicates突然看到2.1999999999999997被删除的副本?

  • 为什么Tally突然看到2.20000000000000062.2明显不同之前呢?


作为一个相关的点,可以看出压缩数组会影响Tally:

a = {2.2000000000000005, 2.2, 2.1999999999999999};
a // InputForm
Tally@a // InputForm
Run Code Online (Sandbox Code Playgroud)
   {2.2000000000000006, 2.2, 2.1999999999999997}
   {{2.2000000000000006`, 3}}
a = Developer`ToPackedArray@a;
a // InputForm
Tally@a // InputForm
Run Code Online (Sandbox Code Playgroud)
   {2.2000000000000006, 2.2, 2.1999999999999997}
   {{2.2000000000000006`, 1}, {2.2, 2}}

WRe*_*ach 11

展出的行为似乎与加上在一些所讨论的功能的一些可疑行为的浮点算术相关联的通常的困境的结果.

SameQ 不是等价关系

首先在板岩上:认为这SameQ不是等价关系,因为它不是传递性的:

In[1]:= $a = {11/5, 2.2000000000000005, 2.2, 2.1999999999999997};

In[2]:= SameQ[$a[[2]], $a[[3]]]
Out[2]= True

In[3]:= SameQ[$a[[3]], $a[[4]]]
Out[3]= True

In[4]:= SameQ[$a[[2]], $a[[4]]]
Out[4]= False                     (* !!! *)
Run Code Online (Sandbox Code Playgroud)

因此,即使在转向其他功能之前,我们也面临着不稳定的行为.

这种行为是由于所记录的规则,SameQ即如果两个实数"最后的二进制数字不同",则将它们视为"相等":

In[5]:= {# // InputForm, Short@RealDigits[#, 2][[1, -10;;]]} & /@ $a[[2;;4]] // TableForm
(* showing only the last ten binary digits for each *)
Out[5]//TableForm= 2.2000000000000006  {0,1,1,0,0,1,1,0,1,1}
                   2.2                 {0,1,1,0,0,1,1,0,1,0}
                   2.1999999999999997  {0,1,1,0,0,1,1,0,0,1}
Run Code Online (Sandbox Code Playgroud)

需要注意的是,严格来说,$a[[3]]$a[[4]]过去不同的2个二进制数字,但不同的幅度最低阶的一位.

DeleteDuplicates不会真正使用SameQ

接下来,考虑文档陈述DeleteDuplicates[...]相当于DeleteDuplicates[..., SameQ].嗯,这是完全正确的 - 但可能不是你可能期望的意义:

In[6]:= DeleteDuplicates[$a] // InputForm
Out[6]//InputForm= {11/5, 2.2000000000000006, 2.2}

In[7]:= DeleteDuplicates[$a, SameQ] // InputForm
Out[7]//InputForm= {11/5, 2.2000000000000006, 2.2}
Run Code Online (Sandbox Code Playgroud)

同样的,如记录的那样......但是这个怎么样:

In[8]:= DeleteDuplicates[$a, SameQ[#1, #2]&] // InputForm
Out[8]//InputForm= {11/5, 2.2000000000000006, 2.1999999999999997}
Run Code Online (Sandbox Code Playgroud)

DeleteDuplicates当比较函数显然SameQ与行为相同的函数相反时,似乎经历了不同的逻辑分支SameQ.

理货是......困惑

Tally 显示类似但不完全相同的不稳定行为:

In[9]:= Tally[$a] // InputForm
Out[9]//InputForm=  {{11/5, 1}, {2.2000000000000006, 1}, {2.2, 2}}

In[10]:= Tally[$a, SameQ] // InputForm
Out[10]//InputForm= {{11/5, 1}, {2.2000000000000006, 1}, {2.2, 2}}

In[11]:= Tally[$a, SameQ[#1, #2]&] // InputForm
Out[11]//InputForm= {{11/5, 1}, {2.2000000000000006, 1}, {2.2000000000000006, 2}}
Run Code Online (Sandbox Code Playgroud)

最后一点特别莫名其妙,因为相同的数字在列表中出现两次,具有不同的计数.

平等遭遇类似问题

现在,回到浮点相等的问题. Equal票价略高于SameQ- 但强调"小". Equal查看最后七位二进制数字而不是最后一位.这并不能解决问题,但总能找到麻烦的案例:

In[12]:= $x1 = 0.19999999999999823;
         $x2 = 0.2;
         $x3 = 0.2000000000000018;

In[15]:= Equal[$x1, $x2]
Out[15]= True

In[16]:= Equal[$x2, $x3]
Out[16]= True

In[17]:= Equal[$x1, $x3]
Out[17]= False             (* Oops *)
Run Code Online (Sandbox Code Playgroud)

恶棍揭露了

所有这些讨论的主要罪魁祸首是浮点实数格式.使用有限格式完全不可能用完整的fildelity表示任意实数.这就是为什么Mathematica强调符号形式并尽可能长时间尝试以符号形式使用表达式.如果一个人发现数字形式是不可避免的,那么就必须涉足那个被称为数值分析的沼泽地来解决涉及平等和不平等的所有角落案例.

可怜的SameQ,Equal,DeleteDuplicates,Tally和他们所有的朋友从未有希望.


Leo*_*rin 9

在我看来,依赖于Tally或者DeleteDuplicates使用默认(SameQ基于类似的)比较函数和数值的任何东西都依赖于实现细节,因为SameQ在数值上没有明确定义的语义.您所看到的是其他语言中通常称为"未定义行为"的内容.应该采取什么措施才能获得可靠的结果

DeleteDuplicates[a,Equal]
Run Code Online (Sandbox Code Playgroud)

要么

Tally[a,Equal]
Run Code Online (Sandbox Code Playgroud)

并且类似地Union(虽然我不会使用,Union因为明确的测试导致它的二次复杂性).OTOH,如果您希望了解内部实现细节,因为您想要使用它们,除了警告这可能造成更多弊大于利之外,我不能说太多,特别是因为这些实现可能会因版本而异 - 即使假设您获得某些特定版本的所有细节.