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

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

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

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

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

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

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

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

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

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

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

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

Ответы

Ответ 1

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

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

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

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

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

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

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

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

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

Если бы не было способов избавиться от NaNs, они были бы бесполезны, как неопределенные на 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). Тот факт, что он уже не говорит о действительности Каханса, что мотивирует нынешнее положение дел.

Ответ 2

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

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

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

Ответ 3

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

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

Ответ 4

Из статьи wikipedia на NaN следующие методы могут вызвать NaNs:

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

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

Ответ 5

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

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

Ответ 6

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

  • (2.7 == 2.7) = true
  • (2.7 == 2.6) = false
  • (2.7 == NaN) = неизвестно
  • (NaN == NaN) = неизвестно

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

Ответ 7

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

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

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

Ответ 8

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

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

Ответ 9

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

Ответ 10

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

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

Ответ 11

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

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

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

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

Ответ 12

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

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

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

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

Ответ 13

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

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

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