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

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

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

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

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

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

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

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

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

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

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

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

13 ответов


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

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

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

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

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

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

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

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


добавление: Многие комментаторы утверждали, что было бы более полезно сохранить рефлексивность равенства и трихотомии на том основании, что принятие 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( ) что может занять много лет. Я процитирую собственный труд Кахана на эту тему:--13-->

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

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


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

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

Это поведение также выгодно в тех случаях, когда вы сравниваете sqrt(-3) С sqrt (-2). Они оба вернут NaN, но они не эквивалентны, хотя они возвращают одно и то же значение. Поэтому равенство всегда возвращает false при работе с 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 по той же причине, что new Error !== new Error;

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


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

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

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

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


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

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

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

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


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

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

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