Каково обоснование для всех сравнений, возвращающих false для значений NaN IEEE754?

267

Почему сравнения значений NaN ведут себя иначе, чем все другие значения? То есть все сравнения с операторами ==, <=,> =, <,>, где одним или обоими значениями является NaN, возвращают false, что противоречит поведению всех других значений.

Я предполагаю, что это каким-то образом упрощает численные вычисления, но я не смог найти явно заявленную причину, даже в « Лекционных заметках о состоянии IEEE 754 » Кахана, в которых подробно обсуждаются другие проектные решения.

Такое отклоняющееся поведение вызывает проблемы при выполнении простой обработки данных. Например, при сортировке списка записей по некоторому вещественному полю в программе на Си мне нужно написать дополнительный код для обработки NaN как максимального элемента, иначе алгоритм сортировки может запутаться.

Изменить: ответы до сих пор все утверждают, что бессмысленно сравнивать NaNs.

Я согласен, но это не значит, что правильный ответ ложный, скорее это был бы не-булев (NaB), которого, к счастью, не существует.

Таким образом, выбор возврата true или false для сравнений, на мой взгляд, является произвольным, и для общей обработки данных было бы полезно, если бы он подчинялся обычным законам (рефлексивность ==, трихотомия <, ==,>), чтобы структуры данных которые полагаются на эти законы, становятся запутанными.

Поэтому я спрашиваю о каком-то конкретном преимуществе нарушения этих законов, а не только философских рассуждениях.

Редактировать 2: Я думаю, что теперь я понимаю, почему сделать максимальный NaN было бы плохой идеей, это испортило бы вычисление верхних пределов.

NaN! = NaN может быть желательно, чтобы избежать обнаружения сходимости в цикле, таких как

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

который, однако, лучше написать, сравнивая абсолютную разницу с небольшим пределом. Так что ИМХО это сравнительно слабый аргумент для нарушения рефлексивности в NaN.

starblue
источник
2
Как только NaN входит в вычисление, оно, как правило, никогда не уходит, поэтому ваш тест на сходимость станет бесконечным циклом. Обычно предпочтительнее сообщать о сбое схождения в вызывающую подпрограмму, возможно, возвращая NaN. Таким образом, структура цикла обычно становится чем-то вроде while (fabs(x - oldX) > threshold)выхода из цикла, если происходит сходимость или NaN входит в вычисление. Обнаружение NaN и соответствующих средств защиты будет происходить за пределами цикла.
Стивен Кэнон
1
Если бы NaN был минимальным элементом порядка, цикл while все равно работал бы.
голубой
2
Пища для размышления: grouper.ieee.org/groups/1788/email/pdfmPSi1DgZZf.pdf стр. 10
синий

Ответы:

535

Я был членом комитета IEEE-754, я постараюсь помочь немного прояснить ситуацию.

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

  • Дополнение не ассоциативно.
  • Распределительный закон не имеет места.
  • Есть числа с плавающей точкой без инверсий.

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

  1. Когда мы можем, мы сопоставляем поведение реальной арифметики.
  2. Когда мы не можем, мы стараемся сделать нарушения максимально предсказуемыми и максимально простыми для диагностики.

Что касается вашего комментария «это не значит, что правильный ответ ложный», это неправильно. Предикат (y < x)спрашивает, yменьше ли x. Если yравно NaN, то оно не меньше, чем любое значение с плавающей запятой x, поэтому ответ обязательно ложный.

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

Возможны четыре взаимоисключающих отношения: меньше, равно, больше и неупорядочено. Последний случай возникает, когда хотя бы один операнд является NaN. Каждый NaN сравнивается неупорядоченным со всем, включая себя.

Что касается написания дополнительного кода для обработки NaN, обычно возможно (хотя и не всегда легко) структурировать ваш код таким образом, чтобы NaN проваливались должным образом, но это не всегда так. Когда это не так, может потребоваться дополнительный код, но это небольшая цена за удобство, которое алгебраическое замыкание принесло арифметике с плавающей точкой.


Приложение: Многие комментаторы утверждают, что было бы более полезно сохранить рефлексивность равенства и трихотомии на том основании, что принятие 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, они были бы такими же бесполезными, как Indefinites на CRAY; как только они встретятся, вычисление лучше остановить, чем продолжать в течение неопределенного времени до неопределенного завершения. Вот почему некоторые операции с NaN должны давать результаты, отличные от NaN. Какие операции? … Исключение составляют предикаты C «x == x» и «x! = X», которые соответственно равны 1 и 0 для каждого бесконечного или конечного числа x, но обращаются, если x не является числом (NaN); они обеспечивают единственное простое исключительное различие между NaN и числами в языках, в которых отсутствует слово для NaN и предикат IsNaN (x).

Обратите внимание, что это также логика, которая исключает возвращение чего-то вроде «Not-A-Boolean». Возможно, этот прагматизм был неуместен, и стандарт должен был это потребовать isnan( ), но это сделало бы практически невозможным эффективное и удобное использование NaN в течение нескольких лет, пока мир ждал принятия языка программирования. Я не уверен, что это был бы разумный компромисс.

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

Стивен Кэнон
источник
16
Я прочитал ваши пункты 1 и 2. Затем я заметил, что в реальной арифметике (расширенной, чтобы позволить NaN в первую очередь) NaN равен самому себе - просто потому, что в математике любая сущность равна себе без исключения. Теперь я в замешательстве: почему IEEE не «соответствует поведению реальной арифметики», что делает NaN == NaN? Чего мне не хватает?
максимум
12
Согласовано; нерефлексивность NaN не создала конца боли для таких языков, как 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" делает вещи проще, чем они были бы в противном случае?
суперкат
5
Теоретически, если у вас был NaN == NaN и нет isNaN, вы все равно можете проверить NaN !(x < 0 || x == 0 || x > 0), но это было бы медленнее и неуклюже, чем x != x.
user2357112 поддерживает Monica
50

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

NaN используется как своего рода заполнитель для этого неопределенного состояния. Математически говоря, undefined не равен undefined. Вы также не можете сказать, что неопределенное значение больше или меньше другого неопределенного значения. Поэтому все сравнения возвращают false.

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

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

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

NaN не содержит информации о том, что является чем-то, только что это не так. Поэтому эти элементы никогда нельзя назвать равными.

Джек Райан
источник
6
Все пустые множества равны по определению.
MSalters
28
Ящики, которые вам выдаются, НЕ известны как пустые.
Джон Смит
7
Вы сказали бы мне, что коробки не содержат то же самое? Я могу понять обоснование для (NaN==Nan)==false. Что я не понимаю, так это обоснование (Nan!=Nan)==true.
суперкат
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-значную логику. Если вы добавите 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 (не число) означает именно это: это не число, и поэтому сравнивать его не имеет смысла.

Это немного похоже на арифметику в SQL с nullоперандами: все они приводят к null.

Сравнения для чисел с плавающей запятой сравнивают числовые значения. Таким образом, они не могут быть использованы для нечисловых значений. Поэтому NaN нельзя сравнивать в числовом смысле.

Дарен Томас
источник
3
«Это не число, и поэтому сравнивать его не имеет смысла». Строки не числа, но их сравнение имеет смысл.
Джейсон
2
да, сравнение строки со строкой имеет смысл. Но сравнивать строку, скажем, с яблоками, не имеет особого смысла. Поскольку яблоки и груши не являются числами, имеет ли смысл их сравнивать? Что больше?
Дарен Томас
@DarenThomas: в SQL нет «IF NULL = NULL THEN FOO; ни "ЕСЛИ НУЛЬ <> НУЛЬ ТОГДА ПОЗВОНИТЕ; [или любой другой синтаксис] будет выполнен FOO. Для NaN, чтобы быть эквивалентным, if (NaN != NaN) foo();не должно выполняться foo, но это выполняется.
суперкат
1

Упрощенный ответ заключается в том, что NaN не имеет числового значения, поэтому в нем нет ничего, с чем можно было бы сравнивать.

Вы можете попробовать проверить и заменить ваши NaN на + INF, если хотите, чтобы они действовали как + INF.

Дэвид Р. Триббл
источник
0

Хотя я согласен, что сравнение NaN с любым действительным числом должно быть неупорядоченным, я думаю, что есть только причина для сравнения NaN с самим собой. Как, например, можно обнаружить разницу между сигнальными NaN и тихими NaN? Если мы рассматриваем сигналы как набор булевых значений (то есть битовый вектор), можно было бы спросить, являются ли битовые векторы одинаковыми или разными, и упорядочить наборы соответствующим образом. Например, при декодировании максимального смещенного показателя, если бы значени и были сдвинуты влево для выравнивания старшего значащего бита значимости и старшего значащего бинарного формата, отрицательным значением будет тихий NaN, а любое положительное значение будет быть сигнальным NaN. Ноль, конечно, зарезервирован для бесконечности, и сравнение будет неупорядоченным. Выравнивание MSB позволило бы напрямую сравнивать сигналы даже из разных двоичных форматов. Следовательно, два NaN с одинаковым набором сигналов будут эквивалентны и придают смысл равенству.

Патрик Кэмпбелл
источник
-1

Для меня самый простой способ объяснить это:

У меня есть что-то, и если это не яблоко, то это апельсин?

Вы не можете сравнить NaN с чем-то другим (даже с самим собой), потому что у него нет значения. Также это может быть любое значение (кроме числа).

У меня есть что-то, и если оно не равно числу, то это строка?

Халил Тевфик
источник
Что вы имеете в виду "это может быть любое значение, кроме числа"?
Пушкин
-2

Потому что математика - это та область, где числа «просто существуют». При вычислениях вы должны инициализировать эти числа и поддерживать их состояние в соответствии с вашими потребностями. В те старые времена инициализация памяти работала так, как вы никогда не могли положиться. Вы никогда не могли позволить себе думать об этом "о, это будет все время инициализироваться с 0xCD, мой алгоритм не сломается" .

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

Это просто смазка, которую вы можете поместить в новую переменную при создании, вместо программирования случайного ада из памяти компьютера. И ваш алгоритм, каким бы он ни был, не сломается.

Затем, когда вы все еще внезапно обнаружите, что ваш алгоритм генерирует NaN, можно очистить его, просматривая каждую ветвь по одному. Опять же, правило «всегда ложное» очень помогает в этом.

sanaris
источник
-4

Очень короткий ответ:

Потому что следующее: не nan / nan = 1 должен держать. В противном случае inf/infбудет 1.

(Следовательно, nanне может быть равным nan. Что касается >или <, если nanбы уважать любое отношение порядка в множестве, удовлетворяющем свойству Архимеда, мы бы снова имели nan / nan = 1предел).

SEF
источник
2
Нет, это не имеет смысла. У нас есть inf = infи inf / inf = nan, так nan = nanчто не помешает nan / nan = nan.
starblue
@starblue Ты имеешь в виду nan / nan = 1? В любом случае ... Ваши рассуждения имеют смысл, если бы inf и nan были такими же, как и любые другие числа. Это не тот случай. Причина, по которой это inf/infдолжно быть nan(или неопределенная форма в математике), а не 1более тонкая, чем простое алгебраическое манипулирование (см. Теорему де Л`Бостала).
SeF