Яке обґрунтування для всіх порівнянь, що повертають помилкові значення IEEE754 NaN?


267

Чому порівняння значень NaN поводиться по-різному від усіх інших значень? Тобто всі порівняння з операторами ==, <=,> =, <,> де одне або обидва значення NaN повертає помилково, всупереч поведінці всіх інших значень.

Я вважаю, що це певним чином спрощує чисельні обчислення, але я не зміг знайти чітко вказану причину, навіть не в Лекційних записках про стан IEEE 754 від Kahan, де детально розглядаються інші проектні рішення.

Така девіантна поведінка викликає проблеми при простому обробці даних. Наприклад, при сортуванні списку записів із деяким значенням реального значення в програмі C мені потрібно написати додатковий код для обробки NaN як максимального елемента, інакше алгоритм сортування може заплутатися.

Редагувати: На сьогодні відповіді всі стверджують, що порівнювати NaNs безглуздо.

Я погоджуюся, але це не означає, що правильна відповідь неправдива, скоріше, це був би не булев (NaB), якого, на щастя, не існує.

Отже, на мій погляд, вибір повернення істинного чи помилкового для порівнянь є довільним, і для загальної обробки даних було б вигідно, якби він відповідав звичайним законам (рефлексивність ==, трихотомія <, ==,>), щоб не було структур даних які спираються на ці закони, плутаються.

Тож я прошу певну перевагу порушення цих законів, а не лише філософських міркувань.

Редагувати 2: Я думаю, я зараз розумію, чому зробити NaN максимумом було б поганою ідеєю, це зіпсувало б обчислення верхніх меж.

NaN! = NaN може бути бажаним, щоб уникнути виявлення конвергенції в циклі, наприклад

while (x != oldX) {
    oldX = x;
    x = better_approximation(x);
}

що, однак, краще записати, порівнюючи абсолютну різницю з невеликою межею. Отже, IMHO - це відносно слабкий аргумент для порушення рефлексивності на NaN.


2
Після того, як NaN увійшов до обчислення, він, як правило, ніколи не вийде, тож ваш тест на конвергенцію стане нескінченним циклом. Зазвичай бажано повідомляти про невдачу конвергенції в рутину виклику, можливо, шляхом повернення NaN. Таким чином, структура циклу, як правило, стає чимось подібним while (fabs(x - oldX) > threshold), виходить із циклу, якщо відбувається конвергенція або в обчислення входить NaN. Виявлення NaN та відповідного засобу може відбуватися поза циклом.
Стівен Канон

1
Якби NaN був мінімальним елементом порядку, тоді цикл все-таки працюватиме.
starblue

2
Їжа для роздумів: grouper.ieee.org/groups/1788/email/pdfmPSi1DgZZf.pdf сторінка 10
starblue

Відповіді:


535

Я був членом комітету IEEE-754, я спробую трохи допомогти з’ясувати речі.

По-перше, числа з плаваючою комою не є дійсними числами, а арифметика з плаваючою комою не відповідає аксіомам реальної арифметики. Трихотомія - не єдина властивість справжньої арифметики, яка не тримає плавців, ані навіть найважливішу. Наприклад:

  • Доповнення не асоціативне.
  • Закон про розподіл не дотримується.
  • Існують числа з плаваючою комою без зворотів.

Я міг би продовжувати. Неможливо вказати арифметичний тип фіксованого розміру, який задовольняє всі властивості реальної арифметики, які ми знаємо і любимо. Комітет 754 повинен вирішити згорнути або зламати деякі з них. Цим керуються деякі досить прості принципи:

  1. Коли ми можемо, ми відповідаємо поведінці реальної арифметики.
  2. Коли ми не можемо, ми намагаємось зробити порушення максимально передбачуваними та простішими для діагностики.

Щодо Вашого коментаря "це не означає, що правильна відповідь неправдива", це неправильно. Присудок (y < x)запитує, чи yменший за x. Якщо yце NaN, то воно не менше будь-якого значення з плаваючою комою x, тому відповідь обов'язково хибна.

Я згадував, що трихотомія не має значення для значень з плаваючою комою. Однак є схожа властивість, яка дійсно дотримується. Пункт 2 статті 5.11 стандарту 754-2008:

Можливі чотири взаємовиключні відносини: менші, рівні, більші та не упорядковані. Останній випадок виникає, коли щонайменше один операнд є NaN. Кожен NaN повинен порівнювати не упорядкований з усім, включаючи себе.

Що стосується написання додаткового коду для обробки NaN, то зазвичай можна (хоча і не завжди просто) структурувати свій код таким чином, щоб NaNs потрапляли належним чином, але це не завжди так. Коли це не так, може знадобитися якийсь додатковий код, але це невелика ціна, щоб заплатити за зручність, яку алгебраїчне закриття принесло арифметиці з плаваючою комою.


Додаток: Багато коментаторів стверджують, що корисніше зберегти рефлексивність рівності та трихотомії на тій підставі, що прийняття NaN! = NaN не схоже на жодну звичну аксіому. Я зізнаюся, що маю певну симпатію до цієї точки зору, тому думав, що перегляну цю відповідь і надам трохи більше контексту.

Я розумію, як говорити з Каханом, що NaN! = NaN виник із двох прагматичних міркувань:

  • Це x == yповинно бути еквівалентно, x - y == 0коли це можливо (крім того, що є теоремою реальної арифметики, це робить апаратну реалізацію порівняння більш просторовою, що було надзвичайно важливим на момент розробки стандарту - зауважте, що це порушено для x = y = нескінченність, тому це не є великою причиною сама по собі; її можна було б розумно схилити (x - y == 0) or (x and y are both NaN)).

  • Що ще важливіше, isnan( )в той час, коли NaN був формалізований в арифметиці 8087 року , не було присудка; необхідно було забезпечити програмістів зручним та ефективним засобом виявлення значень NaN, які не залежали від мов програмування, забезпечуючи щось подібне, isnan( )що може зайняти багато років. Я цитую власне письмо Кахана на цю тему:

Якби не було можливості позбутися від NaN, вони були б такими марними, як Індефініти на КРЕЙ; як тільки хтось зіткнувся, обчислення було б краще зупинити, а не продовжувати на невизначений час до невизначеного висновку. Ось чому деякі операції над NaN повинні забезпечити результати, що не належать до NaN. Які операції? ... Виняток становлять предикати C "x == x" і "x! = X", які відповідно дорівнюють 1 і 0 для кожного нескінченного або кінцевого числа x, але зворотне, якщо x не число (NaN); вони забезпечують єдине просте необмежене розрізнення NaN та чисел у мовах, у яких відсутні слова для NaN та присудка IsNaN (x).

Зауважте, що це також логіка, яка виключає повернення чогось на кшталт "не-булевого". Можливо, цей прагматизм був неправильним, і стандарт повинен був би вимагати isnan( ), але це зробило б NaN майже неможливим ефективно та зручно використовувати протягом декількох років, поки світ чекав на прийняття мови програмування. Я не впевнений, що це було б розумним компромісом.

Якщо бути тупим: результат NaN == NaN зараз не зміниться. Краще навчитися жити з цим, ніж скаржитися в Інтернеті. Якщо ви хочете стверджувати, що також має існувати співвідношення порядку, яке підходить для контейнерів , я б рекомендував рекомендувати, щоб ваша улюблена мова програмування реалізувала totalOrderпредикат, стандартизований в IEEE-754 (2008). Той факт, що він ще не говорить про справедливість турботи Кахана, яка мотивувала сучасний стан справ.


16
Я читав ваші пункти 1 і 2. Потім я помітив, що в реальній арифметиці (розширеній, щоб в першу чергу дозволити NaN) NaN дорівнює самому собі - просто тому, що в математиці будь-яка сутність дорівнює собі, без винятку. Тепер я розгублений: чому IEEE не "відповідав поведінці реальної арифметики", що зробило б NaN == NaN? Що я пропускаю?
макс

12
Домовились; нерефлексивність NaNs не спричинила болю для мов, таких як Python, з його семантикою, що ґрунтується на рівності. Ви дійсно не хочете, щоб рівність не була відношенням до еквівалентності, коли ви намагаєтеся будувати контейнери поверх неї. І наявність двох окремих понять рівності теж не є доброзичливим варіантом для мови, яку слід легко вивчити. Результат (у випадку з Python) - неприємно крихкий компроміс між повагою до IEEE 754 та не надто порушеною семантикою стримування. На щастя, рідко поміщати NaN в контейнери.
Марк Дікінсон

5
Деякі приємні спостереження тут: bertrandmeyer.com/2010/02/06/…
Марк Дікінсон

6
@StephenCanon: Яким чином (0/0) == (+ INF) + (-INF) буде більш безглуздим, ніж наявність 1f/3f == 10000001f/30000002f? Якщо значення з плаваючою комою вважаються класами еквівалентності, то a=bце не означає "Обчислення, які дали результат, aі bякщо вони будуть виконані з нескінченною точністю, дадуть однакові результати", а скоріше "Що відомо про aзбіг з тим, що відомо про b". Мені цікаво, якщо ви знаєте будь-які приклади коду, де наявність "Nan! = NaN" робить речі простішими, ніж було б інакше?
supercat

5
Теоретично, якби у вас був NaN == NaN і немає noNaN, ви все ще могли б протестувати на NaN !(x < 0 || x == 0 || x > 0), але це було б повільніше і незграбніше, ніж x != x.
user2357112 підтримує Моніку

50

NaN можна розглядати як невизначений стан / число. подібно до поняття 0/0 як невизначене або sqrt (-3) (у системі реальних чисел, де живе плаваюча точка).

NaN використовується як своєрідний заповнювач для цього невизначеного стану. Математично кажучи, невизначене не дорівнює визначенню. Не можна також сказати, що невизначене значення більше або менше, ніж інше невизначене значення. Тому всі порівняння повертаються помилковими.

Така поведінка вигідна також у випадках, коли ви порівнюєте sqrt (-3) з sqrt (-2). Вони обидва повернуть NaN, але вони не еквівалентні, хоча вони повертають однакове значення. Тому мати рівність, яка завжди повертається помилкою при роботі з NaN - бажана поведінка.


5
Яким повинен бути результат sqrt (1.00000000000000022) == sqrt (1.0)? Як щодо (1E308 + 1E308-1E308-1E308-1E308) == (1E308 + 1E308)? Крім того, лише п’ять із шести порівнянь повертаються помилковими. !=Оператор повертає істину. Наявність NaN==NaNі NaN!=NaNповернення помилок дозволить коду, який порівнює x і y, вибирати, що має відбутися, коли обидва операнди є NaN, вибравши або ==або !=.
supercat

38

Накинути ще одну аналогію. Якщо я вручу вам дві скриньки і скажу вам, що жодна з них не містить яблука, ви б сказали мені, що в ящиках є те саме?

NaN не містить жодної інформації про те, що є, а що не. Тому цих елементів ніколи не можна сказати рівними.


6
Усі порожні множини за визначенням рівні.
MSalters

28
Подані вами коробки НЕ відомі, що вони порожні.
Джон Сміт

7
Скажіть, ящики не містять того самого? Я можу зрозуміти обґрунтування (NaN==Nan)==false. Те, що я не розумію, є обґрунтуванням (Nan!=Nan)==true.
supercat

3
Я припускаю, що NaN! = NaN є істинним, тому що x! = Y визначається як! (X == y). Зрозуміло, я не знаю, чи специфікує IEEE саме так.
Кеф Шектер

6
Але в цій аналогії, якщо ви дали мені коробку, сказали, що вона не містить яблук, то запитали мене, чи рівна вона сама собі, ви очікуєте, що я скажу ні? Тому що це я мав би сказати згідно IEEE.
крапка з комою

12

З статті вікіпедії про NaN такі NaN можуть спричинити такі практики:

  • Всі математичні операції> з NaN як мінімум один операнд
  • Підрозділи 0/0, ∞ / ∞, ∞ / -∞, -∞ / ∞ і -∞ / -∞
  • Множення 0 × ∞ і 0 × -∞
  • Додавання ∞ + (-∞), (-∞) + ∞ та еквівалентні віднімання.
  • Застосування функції до аргументів поза її доменом, включаючи взяття квадратного кореня від'ємного числа, прийняття логарифму від'ємного числа, взяття дотичної непарного кратного 90 градусів (або π / 2 радіану) або прийняття зворотного синуса або косинус числа, який менше -1 або більше +1.

Оскільки немає можливості дізнатися, яку з цих операцій створив NaN, немає можливості порівняти їх, що має сенс.


3
Більше того, навіть якби ви знали, яка операція, це не допоможе. Я можу побудувати будь-яку кількість формул, які в якийсь момент переходять до 0/0, які мають (якщо вважати безперервність) чітко визначені та різні значення на той момент.
Девід Торнлі

4

Я не знаю обґрунтування дизайну, але ось уривок зі стандарту IEEE 754-1985:

"Можна буде порівнювати числа з плаваючою комою у всіх підтримуваних форматах, навіть якщо формати операндів відрізняються. Порівняння точні і ніколи не переповнюються і не переливаються. Можливі чотири взаємовиключні відносини: менше, рівне, більше і не упорядковане Останній випадок виникає, коли щонайменше один операнд є NaN. Кожен NaN повинен порівнювати не упорядкований з усім, включаючи себе ".


2

Це виглядає лише своєрідно, оскільки більшість програмних середовищ, які дозволяють NaN, також не допускають 3-значущої логіки. Якщо ви кинете в суміш тризначну логіку, вона стає послідовною:

  • (2,7 == 2,7) = вірно
  • (2,7 == 2,6) = помилково
  • (2,7 == NaN) = невідомо
  • (NaN == NaN) = невідомо

Навіть .NET не надає bool? operator==(double v1, double v2)оператора, тому ви все ще застрягли з дурним (NaN == NaN) = falseрезультатом.


1

Я здогадуюсь, що NaN (Not A Number) означає саме це: Це не число, а тому порівнювати його насправді не має сенсу.

Це трохи схоже на арифметику в SQL з nullоперандами: всі вони призводять до null.

Порівняння чисел з плаваючою комою порівнюють числові значення. Таким чином, їх не можна використовувати для нечислових значень. Тому NaN не можна порівнювати в числовому значенні.


3
"Це не число, і тому порівнювати його насправді не має сенсу". Рядки не є числами, але порівнювати їх має сенс.
Ясон

2
так, порівнювати рядок із рядком має сенс. Але порівнювати рядок з, скажімо, яблуками, не має великого сенсу. Оскільки яблука та груші - це не цифри, чи є сенс їх порівнювати? Що більше?
Дарен Томас

@DarenThomas: У SQL ні "IF NULL = NULL THOO FOO;" ні "ЯКЩО НУЛЬТЕ <> НУЛЬТЕ НАЗВАЙТЕ ФОТО;" [або все, що є синтаксисом] буде виконано FOO. Щоб NaN був еквівалентним, if (NaN != NaN) foo();його не слід виконувати foo, але це так.
supercat

1

Занадто спрощена відповідь полягає в тому, що NaN не має числового значення, тому в ньому немає нічого порівняння з чим-небудь іншим.

Ви можете розглянути можливість тестування та заміни своїх NaN на + INF, якщо ви хочете, щоб вони діяли як + INF.


0

Хоча я згоден, що порівняння NaN з будь-яким реальним числом повинно бути не упорядкованим, я думаю, що для порівняння NaN із самим собою є просто причина. Як, наприклад, можна виявити різницю між сигналами NaN та тихими NaN? Якщо ми розглядаємо сигнали як сукупність булевих значень (тобто бітового вектора), можна запитати, чи є бітові вектори однакові чи різні, і упорядкувати набори відповідно. Наприклад, при декодуванні максимально упередженого експонента, якщо значення знамення і ліворуч зміщені так, щоб вирівняти найзначніший біт ознаки на найзначніший біт бінарного формату, від'ємне значення буде тихим NaN і будь-яке позитивне значення бути сигнальним NaN. Нуль, звичайно, зарезервований для нескінченності, і порівняння було б не упорядкованим. Вирівнювання MSB дозволило б безпосередньо порівняти сигнали навіть з різних бінарних форматів. Отже, два NaN з однаковим набором сигналів були б рівнозначними і давали б значення рівності.


-1

Для мене найпростіший спосіб пояснити це:

У мене щось є, і якщо це не яблуко, то це апельсин?

Ви не можете порівнювати NaN з чимось іншим (навіть самим собою), оскільки він не має значення. Також це може бути будь-яке значення (крім числа).

У мене щось є, і якщо воно не дорівнює числу, то це рядок?


Що ви маєте на увазі "це може бути будь-яке значення, крім числа"?
пушкін

-2

Тому що математика - це поле, де числа "просто існують". Виконуючи обчислення, ви повинні ініціалізувати ці числа та зберігати їх стан відповідно до ваших потреб. У ті часи ініціалізація пам'яті працювала так, як ви ніколи не могли розраховувати. Ви ніколи не могли дозволити собі думати про це "о, це було б ініціалізовано з 0xCD весь час, моє альго не зламається" .

Тому вам потрібний належний розчинник, що не змішується, який є досить липким, щоб не допустити всмоктування та розбиття алгоритму. Хороші алгоритми, що включають числа, здебільшого будуть працювати з відносинами, а ті, якщо () відносини будуть опущені.

Це просто змащення, яке ви можете вставити в нову змінну при створенні, а не програмувати випадкове пекло з пам'яті комп'ютера. І ваш алгоритм, який би він не був, не порушиться.

Далі, коли ви все-таки раптом дізнаєтесь, що ваш алгоритм виробляє NaN, можна очистити його, оглядаючи кожну гілку по одному. Знову ж таки, правило "завжди помилкове" дуже допомагає в цьому.


-4

Дуже коротка відповідь:

Тому що наступне: nan / nan = 1 НЕ повинен утримуватись. Інакше inf/infбуло б 1.

(Тому nanне можна дорівнювати nan. Що стосується >або <, якщо nanб поважали будь-яке відношення порядку в наборі, що задовольняє властивість Архімеда, ми знову мали б nan / nan = 1межу).


2
Ні, це не має сенсу. У нас є inf = infі inf / inf = nan, тому nan = nanне завадить nan / nan = nanні.
starblue

@starblue Ви маєте на увазі nan / nan = 1? У будь-якому разі ... Ваші міркування мають сенс, якби inf та nan були такими ж, як і будь-які інші числа. Це не так. Причина, що inf/infповинна бути nan(або невизначеною формою в математиці), а не 1є більш тонкою, ніж проста алгебраїчна маніпуляція (див. Теорему Де Л'Госпіталь).
СеФ
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.