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


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

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

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

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

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

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

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

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

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

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

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

13 218

13 ответов:

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

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

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

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

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

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

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

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

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


добавление: Многие комментаторы утверждали, что было бы более полезно сохранить рефлексивность равенства и трихотомии на том основании, что принятие NaN != Нэн, похоже, не сохраняет ни одной знакомой аксиомы. Я признайтесь, что у меня есть некоторое сочувствие к этой точке зрения, поэтому я подумал, что вернусь к этому ответу и предоставлю немного больше контекста.

мое понимание от разговора с Каханом - это НАН != НАН возникла из двух прагматических соображений:

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

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

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

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

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

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

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

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

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

из статьи Википедии о NaN, следующие действия могут вызвать NaNs:

  • все математические операции> с NaN как минимум один операнд
  • деления 0/0, ∞/∞, ∞/-∞, -∞/∞, и -∞/ -
  • умножения 0×∞ и 0×-∞
  • дополнения ∞ + (-∞), (-∞) + ∞ и эквивалентные вычитания.
  • применение функции к аргументам вне ее домена, включая взятие квадратного корня из отрицательного значения число, берущее логарифм отрицательного числа, берущее тангенс нечетного кратного 90 градусов (или π/2 Радиана), или берущее обратный синус или Косинус числа, которое меньше -1 или больше +1.

поскольку нет способа узнать, какая из этих операций создала NaN, нет способа сравнить их, что имеет смысл.

Я не знаю обоснование дизайна, но вот выдержка из стандарта IEEE 754-1985:

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

это только выглядит странно, потому что большинство сред программирования, которые позволяют NaNs также не позволяют 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 результат.

Я предполагаю, что NaN (не число) означает именно это: это не число, и поэтому сравнение его на самом деле не имеет смысла.

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

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

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

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

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

NaN-это неявный новый экземпляр (особого рода ошибки времени выполнения). Это значит NaN !== NaN по той же причине, что new Error !== new Error;

и имейте в виду, что такая имплицитность также видна вне ошибок, например, в контексте регулярных выражений это означает /a/ !== /a/ который просто синтаксический сахар для new RegExp('a') !== new RegExp('a')

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

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

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

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

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

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

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

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

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

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

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