Нестабильность в DeleteDuplicates и Tally - PullRequest
11 голосов
/ 29 мая 2011

При подготовке ответа на Подсчитайте, сколько разных значений принимает список в 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}}

Ответы [ 2 ]

11 голосов
/ 29 мая 2011

Показанное поведение, по-видимому, является результатом обычных проблем, связанных с арифметикой с плавающей запятой, в сочетании с некоторым сомнительным поведением в некоторых из обсуждаемых функций.

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 и все их друзья никогда не имели шансов.

9 голосов
/ 29 мая 2011

По моему мнению, полагаться на что-либо для Tally или DeleteDuplicates со стандартной (SameQ -подобной) функцией сравнения и числовыми значениями полагается на детали реализации, потому что SameQ не имеет четко определенного семантика числовых значений. То, что вы видите, это то, что обычно называют «неопределенным поведением» в других языках. Чтобы получить надежные результаты, нужно использовать

DeleteDuplicates[a,Equal]

или

Tally[a,Equal]

и аналогично для Union (хотя я бы не использовал Union, поскольку явный тест приводит к квадратичной сложности для него). OTOH, если вы хотите понять внутренние детали реализации, потому что вы хотите их использовать, я не могу сказать ничего, кроме как предупредить, что это может принести больше вреда, чем пользы, особенно потому, что эти реализации могут изменяться от версии к версии - даже если предположить, что вы получите все их данные прямо для какой-то конкретной версии.

...