在准备Count how many different values a list takes in Mathematica的答案时,我在DeleteDuplicates
和Tally
中遇到了一个我不明白的不稳定(缺乏更好的术语)。
首先考虑:
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
补偿了微小的数值差异,并将每个元素视为等效。 Union
和DeleteDuplicates
将所有元素视为唯一。 (根据我的知识,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
的输出符合预期,但DeleteDuplicates
和Tally
的结果令人惊讶。
为什么DeleteDuplicates
突然看到2.1999999999999997
作为要删除的副本?
为什么Tally
突然看到2.2000000000000006
和2.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}}
答案 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,以解决涉及平等和不平等的所有角落案例。
差SameQ
,Equal
,DeleteDuplicates
,Tally
,所有朋友都没有机会。
答案 1 :(得分:9)
在我看来,依赖Tally
或DeleteDuplicates
与默认(SameQ
- 类似)比较函数和数值的任何内容都依赖于实现细节,因为{{1} }对数值没有明确定义的语义。您所看到的是其他语言中通常称为“未定义行为”的内容。为了获得可靠的结果,应该做的是使用
SameQ
或
DeleteDuplicates[a,Equal]
和Tally[a,Equal]
类似(虽然我不会使用Union
,因为显式测试会导致二次复杂度)。 OTOH,如果您希望了解内部实现细节,因为您想要使用它们,除了警告这可能造成更多弊大于利之外,我不能说太多,特别是因为这些实现可能会因版本而异 - 即使假设您获得了某些特定版本的所有详细信息。