【问题标题】:Instability in DeleteDuplicates and TallyDeleteDuplicates 和 Tally 不稳定
【发布时间】:2011-05-29 09:22:19
【问题描述】:

在准备对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}}

【问题讨论】:

    标签: wolfram-mathematica duplicates precision


    【解决方案1】:

    所展示的行为似乎是与浮点运算相关的常见问题以及正在讨论的某些函数中的一些可疑行为的结果。

    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]] 在最后两个二进制数字上有所不同,但差异的大小是最低位的一位。

    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}
    

    当比较函数明显是 SameQ 而不是行为与 SameQ 相同的函数时,DeleteDuplicates 似乎经历了不同的逻辑分支。

    Tally 是……困惑

    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 也有类似的问题

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

    揭开面具的恶棍

    所有这些讨论的罪魁祸首是浮点实数格式。使用有限格式完全保真地表示任意实数是不可能的。这就是为什么 Mathematica 强调符号形式并尽可能长时间地使用符号形式的表达式。如果一个人发现数字形式是不可避免的,那么就必须涉足那个名为numerical analysisswamp,以理清所有涉及相等和不等式的极端情况。

    可怜的SameQEqualDeleteDuplicatesTally 和他们所有的朋友都没有机会。

    【讨论】:

    • +1 - 非常好的讨论,谢谢!从实际的角度来看,我仍然打赌Equal 将比SameQ 处理更多的案件,相当令人满意。一个更好定义的问题是基于一些刚性网格(bin 集)定义等价类,如果两个数字最终位于同一个“bin”中,则认为它们相等。这是临时性的,但定义明确,对于许多问题来说可能并没有那么不合理。
    • @Leonid 和 WReach,Order[#, #2]===0 & 是 SameQ-And-I-Really-Mean-It-This-Time 的最佳选择吗?
    • @Mr.Wizard 我不认为以您建议的方式使用Order 解决了@WReach 讨论的任何类型问题,这是SameQ 所固有的。两者都用于符号比较。 Order的意义在于它是一个比较函数,而不是等价关系。这是一个更强的条件——例如,Union 只给定SameQ 只能是二次复杂度(成对比较),而使用Order 原则上可以是n log n,因为Order 可以是用于对列表进行排序(实际上内置的Union 将始终是n 中的二次方,唉)。
    • @Mr.Wizard Order 的文档没有提及此事,但实验表明 Order 正在对浮点数进行按位比较。特别是,它似乎没有执行SameQ 正在使用的相同的“1 位软糖”。所以,我同意Order 似乎是ReallySameFloatQ 的一个很好的替代品(在相同位的意义上)——至少在世界资源研究所决定改变它之前!为了真正安全,可以实现MySameQ,在比较浮点数之前将它们转换为位的一些规范表示。
    • @Mr.Wizard 我真的很惊讶SameQ 1 位软糖——我原以为它会做一个简单的按位比较,留下“捏造”到Equal
    【解决方案2】:

    在我看来,依赖于 TallyDeleteDuplicates 的默认值(基于SameQ)比较函数和数值依赖于实现细节,因为SameQ 没有明确定义数值的语义。您看到的是其他语言中通常称为“未定义行为”的内容。要获得稳健的结果,应该做的是使用

    DeleteDuplicates[a,Equal]
    

    Tally[a,Equal]
    

    对于Union 也是如此(尽管我不会使用Union,因为显式测试会导致它的二次复杂度)。 OTOH,如果您想了解内部实现细节因为您想使用它们,我不能说太多,除了警告这可能弊大于利,特别是因为这些实现可能会因版本而异- 甚至假设您针对某个特定版本了解了他们的所有详细信息。

    【讨论】:

    • 题外话:到目前为止,没有一次访问此站点时没有了解有关 mma 的新知识。
    • 我猜“未定义的行为”是合理的,但不知何故我期望更多的一致性。我想这就是“未定义”的含义。我想这只是运气,我能够以我的方式使用默认的Tally
    • DeleteDuplicates[a, Equal] 似乎也默认为默认值,与DeleteDuplicates[a, Equal[##]&]不同
    • @Rojo 这很奇怪。听起来像一个错误。
    猜你喜欢
    • 2015-01-30
    • 1970-01-01
    • 2020-07-07
    • 1970-01-01
    • 2017-05-11
    • 2011-01-20
    • 2018-11-26
    • 2016-10-18
    • 2011-01-16
    相关资源
    最近更新 更多