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

StackOverflow https://stackoverflow.com/questions/1565164

Вопрос

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

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

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

Редактировать: Все ответы на данный момент утверждают, что сравнивать NANS бессмысленно.

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

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

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

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

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

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

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

Это было полезно?

Решение

Я был членом комитета 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, они были бы так же бесполезны, как Неопределенные значения на CRAYs;как только один из них был обнаружен, вычисление лучше всего было бы остановить, а не продолжать в течение неопределенного времени до Неопределенного завершения.Вот почему некоторые операции с NaNs должны давать результаты, отличные от NaN.Какие операции?... Исключением являются предикаты C “ x == x ” и “ x != x ”, которые равны соответственно 1 и 0 для каждого бесконечного или конечного числа x, но обратны, если x не является числом ( NaN );они обеспечивают единственное простое, не требующее исключения различие между NaN и числами в языках, в которых отсутствует слово для обозначения NaN и предикат isNaN(x).

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

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

Другие советы

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top