DeleteDuplicates和Tally中的不稳定性

时间:2011-05-29 09:22:19

标签: wolfram-mathematica duplicates precision

在准备Count how many different values a list takes in Mathematica的答案时,我在DeleteDuplicatesTally中遇到了一个我不明白的不稳定(缺乏更好的术语)。

首先考虑:

a = {2.2000000000000005, 2.2, 2.1999999999999999};

a // InputForm
DeleteDuplicates@a // InputForm
Union@a // InputForm
Tally@a // InputForm
   {2.2000000000000006`, 2.2, 2.1999999999999997`}
   {2.2000000000000006`, 2.2, 2.1999999999999997`}
   {2.1999999999999997`, 2.2, 2.2000000000000006`}
   {{2.2000000000000006`, 3}}

此行为与我在每种情况下的预期相同。 Tally补偿了微小的数值差异,并将每个元素视为等效。 UnionDeleteDuplicates将所有元素视为唯一。 (根据我的知识,Tally的这种行为没有记录,但我之前已经使用过它。)

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

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

a // InputForm
DeleteDuplicates@a // InputForm
Union@a // InputForm
Tally@a // InputForm
   {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
   {2.2000000000000006, 2.2, 2.1999999999999997}
   {{2.2000000000000006`, 3}}
a = Developer`ToPackedArray@a;
a // InputForm
Tally@a // InputForm
   {2.2000000000000006, 2.2, 2.1999999999999997}
   {{2.2000000000000006`, 1}, {2.2, 2}}

2 个答案:

答案 0 :(得分: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                     (* !!! *)

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

此行为是由于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}

请注意,严格来说,$a[[3]]$a[[4]]在最后的两个二进制数字中有所不同,但差异的幅度是最低阶的一位。< / p>

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}

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

In[8]:= DeleteDuplicates[$a, SameQ[#1, #2]&] // InputForm
Out[8]//InputForm= {11/5, 2.2000000000000006, 2.1999999999999997}

当比较函数显然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}}

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

平等遭受类似问题

现在,回到浮点相等的问题。 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 *)

恶棍揭露

所有讨论中的主要罪魁祸首是浮点实数格式。使用有限格式完全不可能用完整的fildelity表示任意实数。这就是为什么Mathematica强调符号形式并尽可能长时间尝试以符号形式使用表达式。如果发现数字形式是不可避免的,那么必须涉及swamp numerical analysis,以解决涉及平等和不平等的所有角落案例。

SameQEqualDeleteDuplicatesTally,所有朋友都没有机会。

答案 1 :(得分:9)

在我看来,依赖TallyDeleteDuplicates与默认(SameQ - 类似)比较函数和数值的任何内容都依赖于实现细节,因为{{1} }对数值没有明确定义的语义。您所看到的是其他语言中通常称为“未定义行为”的内容。为了获得可靠的结果,应该做的是使用

SameQ

DeleteDuplicates[a,Equal]

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