Почему NaN не равен NaN?

Соответствующий стандарт IEEE определяет числовую константу NaN (а не число) и предписывает, чтобы NaN сравнивался как не равный себе. Почему это?

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

Стандарты IEEE хорошо продуманны, поэтому я уверен, что есть веская причина, почему сравнение NaN как равное с самим собой было бы плохим. Я просто не могу понять, что это такое.

Ответ 1

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

Правильный ответ дается здесь:

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

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

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

Если комитет предвидел будущие варианты использования и считал их достаточно важными, они могли бы пойти на более подробный !(x<x & x>x) вместо x!=x в качестве теста для NaN. Тем не менее, их внимание было более прагматичным и узким: обеспечение наилучшего решения для числовых вычислений, и поэтому они не видели проблем с их подходом.

===

Оригинальный ответ:

Прошу прощения, так как я ценю мысль, которая вошла в верхний голос, я не согласен с этим. NaN не означает "undefined" - см. http://www.cs.berkeley.edu/~wkahan/ieee754status/IEEE754.PDF, стр. 7 (поиск слова "undefined" ). Как подтверждает этот документ, NaN является четко определенной концепцией.

Кроме того, подход IEEE заключался в том, чтобы как можно больше следовать правилам обычной математики, и когда они не могли, следуйте правилу "наименьшего удивления" - см. fooobar.com/questions/5743/.... Любой математический объект равен самому себе, поэтому правила математики означают, что NaN == NaN должно быть истинным. Я не вижу никакой веской и веской причины отклоняться от такого крупного математического принципа (не говоря уже о менее важных правилах трихотомии сравнения и т.д.).

В результате мой вывод таков.

Члены комитета IEEE не очень хорошо это поняли и допустили ошибку. Поскольку очень немногие люди понимали подход комитета IEEE или заботились о том, что именно стандарт говорит о NaN (а именно: большинство обработок компиляторов NaN в любом случае нарушает стандарт IEEE), никто не поднял тревогу. Следовательно, эта ошибка теперь включена в стандарт. Это вряд ли будет исправлено, поскольку такое исправление сломает много существующего кода.

Изменить: Вот одно сообщение из очень содержательного обсуждения. Примечание. Чтобы получить объективный вид, вы должны прочитать весь поток, так как Guido придерживается другого взгляда на мнение других разработчиков ядра. Тем не менее, Гвидо лично не заинтересован в этой теме и в значительной степени следует за рекомендацией Тима Петерса. Если у кого есть аргументы Тима Питерса в пользу NaN != NaN, добавьте их в комментарии; у них есть хороший шанс изменить мое мнение.

Ответ 2

Принятый ответ 100% без вопросов WRONG. Не на полпути неправильно или даже немного неправильно. Я боюсь, что эта проблема будет запутывать и вводить в заблуждение программистов в течение длительного времени, когда этот вопрос появится в результатах поиска.

NaN предназначен для распространения по всем вычислениям, заражая их, как вирус, поэтому, если где-то в ваших глубоких сложных вычислениях вы попадаете на NaN, вы не вызываете кажущегося разумного ответа. В противном случае с помощью тождества NaN/NaN должно равняться 1 вместе со всеми другими последствиями, такими как (NaN/NaN) == 1, (NaN * 1) == NaN и т.д. Если вы представляете, что ваши расчеты поступили не так где-то (округление получило нулевой знаменатель, дающий NaN) и т.д., тогда вы можете получить дико ошибочный (или, что еще хуже: тонко неверный) результат ваших вычислений без очевидного индикатора того, почему.

Есть также действительно веские причины для NaN в вычислениях при исследовании значения математической функции; одним из примеров, приведенных в связанном документе, является поиск нулей() функции f(). Вполне возможно, что в процессе исследования функции с значениями угадывания вы будете зондировать, где функция f() не дает никакого разумного результата. Это позволяет нулям() видеть NaN и продолжать работу.

Альтернативой NaN является инициирование исключения, как только встречается незаконная операция (также называемая сигналом или ловушкой). Помимо серьезных штрафов за производительность, с которыми вы могли столкнуться, в то время не было гарантии, что ЦП будут поддерживать его на аппаратном уровне, или OS/язык будет поддерживать его в программном обеспечении; каждый был своей уникальной снежинкой в ​​обработке плавающей запятой. IEEE решил явно обрабатывать его в программном обеспечении как значения NaN, чтобы он был переносимым на любую ОС или язык программирования. Правильные алгоритмы с плавающей запятой, как правило, правильны во всех реализациях с плавающей запятой, будь то node.js или COBOL (hah).

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

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

"Интервью со стажем плавающей точки"

"История формата IEEE с плавающей запятой"

Что каждый компьютерный ученый должен знать о арифметике с плавающей запятой

Ответ 3

Ну, log(-1) дает NaN, а acos(2) также дает NaN. Означает ли это, что log(-1) == acos(2)? Совершенно очевидно. Следовательно, имеет смысл, что NaN не равно самому себе.

Повторяя это почти два года спустя, здесь функция сравнения "NaN-safe":

function compare(a,b) {
    return a == b || (isNaN(a) && isNaN(b));
}

Ответ 4

Хорошее свойство: if x == x возвращает false, тогда x есть NaN.

(можно использовать это свойство, чтобы проверить, является ли x NaN или нет.)

Ответ 5

Попробуйте следующее:

var a = 'asdf';
var b = null;

var intA = parseInt(a);
var intB = parseInt(b);

console.log(intA); //logs NaN
console.log(intB); //logs NaN
console.log(intA==intB);// logs false

Если intA == intB были истинными, это может привести к выводу, что a == b, что явно не так.

Другим способом взглянуть на это является то, что NaN просто дает вам информацию о том, что что-то НЕ, а не о том, что это такое. Например, если я скажу, что "яблоко не горилла" и "оранжевый не горилла", вы бы пришли к выводу, что "яблоко" - "оранжевое"?

Ответ 6

На самом деле в математике есть понятие, известное как "единство". Эти значения являются расширениями, которые тщательно сконструированы для согласования внешних проблем в системе. Например, вы можете думать о бесконечном кольце в сложной плоскости как о точке или множестве точек, а некоторые ранее претенциозные проблемы уходят. Существуют и другие примеры этого в отношении мощностей множеств, где вы можете продемонстрировать, что вы можете выбрать структуру континуума бесконечностей, пока | P (A) | > | A | и ничего не сломается.

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Я только работаю с моей смутной памятью о некоторых интересных предостережениях во время моих математических исследований. Прошу прощения, если я сделал ужасную работу по представлению концепций, о которых я говорил выше.

Если вы хотите верить, что NaN - это одиночная ценность, то вы, вероятно, будете недовольны некоторыми результатами, такими как оператор равенства, который не работает так, как вы ожидаете/хотите. Однако, если вы решите поверить, что NaN является скорее континуумом "плохости", представленным одиночным заполнителем, тогда вы совершенно удовлетворены поведением оператора равенства. Другими словами, вы теряете из виду рыбу, которую вы поймали в море, но вы поймаете другую, которая выглядит одинаково, но такая же вонючая.