Показанное поведение, по-видимому, является результатом обычных проблем, связанных с арифметикой с плавающей запятой, в сочетании с некоторым сомнительным поведением в некоторых из обсуждаемых функций.
SameQ Is Не Отношение эквивалентности
Сначала на листе: учтите, что 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]]
отличаются последними двумя двоичными цифрами, но величина разности составляет один бит самого младшего разряда.
Удалить дубликаты не Действительно Использовать 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 is ... Confused
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 *)
Злодей без масок
Основным виновником всего этого обсуждения является формат вещественных чисел с плавающей точкой. Просто невозможно представить произвольные действительные числа в полной сложности, используя конечный формат. Вот почему Mathematica подчеркивает символическую форму и делает все возможное, чтобы как можно дольше работать с выражениями в символической форме. Если кто-то считает, что числовые формы неизбежны, тогда нужно вникнуть в это болото , называемое численный анализ , чтобы разобраться во всех угловых случаях, связанных с равенством и неравенством.
Бедные SameQ
, Equal
, DeleteDuplicates
, Tally
и все их друзья никогда не имели шансов.