Почему NaN не равен NaN?
Соответствующий стандарт IEEE определяет числовую константу NaN (не число) и предписывает, чтобы NaN сравнивалось как не равное себе. Это почему?
Все языки, с которыми я знаком, реализуют это правило. Но это часто вызывает серьезные проблемы, например, неожиданное поведение, когда NaN хранится в контейнере, когда NaN находится в данных, которые сортируются, и т. Д. Не говоря уже о том, что подавляющее большинство программистов ожидают, что любой объект будет равен самому себе (прежде чем они узнают о NaN), поэтому их удивление добавляет ошибок и путаницы.
Стандарты IEEE хорошо продуманы, поэтому я уверен, что есть веская причина, по которой сравнение NaN как равного себе было бы плохим. Я просто не могу понять, что это такое.
6 ответов
Мой оригинальный ответ (от 4 лет назад) критикует решение с современной точки зрения без понимания контекста, в котором было принято решение. Таким образом, это не отвечает на вопрос.
Правильный ответ дан здесь:
NaN
знак равноNaN
возник из двух прагматических соображений:[...] Там не было
isnan( )
предикат в то время, когда NaN был формализован в арифметике 8087; было необходимо предоставить программистам удобные и эффективные средства обнаружения значений 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 состоял в том, чтобы как можно больше следовать обычным правилам математики, а когда они не могли, следовать правилу "наименьшего удивления" - см. /questions/30292342/kakovo-obosnovanie-dlya-vseh-sravnenij-vozvraschayuschih-lozh-dlya-znachenij-nan-ieee754/30292358#30292358. Любой математический объект равен самому себе, поэтому правила математики подразумевают, что NaN == NaN должно быть True. Я не вижу никакой веской и веской причины отклоняться от такого важного математического принципа (не говоря уже о менее важных правилах трихотомии сравнения и т. Д.).
В результате мой вывод таков.
Члены комитета IEEE не очень четко продумали это и допустили ошибку. Поскольку очень немногие люди понимали подход комитета IEEE или интересовались тем, что именно стандарт говорит о NaN (то есть, большинство обработчиков NaN нарушают стандарт IEEE в любом случае), никто не поднял тревогу. Следовательно, эта ошибка теперь включена в стандарт. Это вряд ли будет исправлено, так как такое исправление может нарушить существующий код.
Редактировать: вот один пост из очень информативного обсуждения. Примечание: чтобы получить беспристрастное представление, вы должны прочитать всю ветку, поскольку Guido придерживается мнения, отличного от представления некоторых других разработчиков ядра. Тем не менее, Гвидо лично не заинтересован в этой теме и во многом следует рекомендациям Тима Питерса. Если у кого-то есть аргументы Тима Питерса в пользу NaN != NaN
пожалуйста, добавьте их в комментариях; у них есть хороший шанс изменить мое мнение.
Принятый ответ на 100% без вопросов НЕПРАВИЛЬНО. Не наполовину неправильно или даже немного неправильно. Я боюсь, что эта проблема может запутать и ввести в заблуждение программистов на долгое время, когда этот вопрос всплывает в поисках.
NaN предназначен для распространения по всем вычислениям, заражая их, как вирус, поэтому, если где-то в ваших глубоких и сложных вычислениях вы натолкнетесь на NaN, вы не получите, казалось бы, разумного ответа. В противном случае по тождеству NaN/NaN должен равняться 1, вместе со всеми другими последствиями, такими как (NaN/NaN)==1, (NaN*1)==NaN и т. Д. Если вы предполагаете, что ваши вычисления где-то пошли не так (округление привело к нулевой знаменатель, уступающий NaN) и т. д., тогда вы можете получить совершенно неверные (или, что еще хуже: слегка неверные) результаты ваших расчетов без очевидного указания на то, почему.
Существуют также веские причины для использования NaN в вычислениях при определении значения математической функции; Одним из примеров, приведенных в связанном документе, является поиск нулей () функции f(). Вполне возможно, что при проверке функции со значениями угадывания вы будете проверять точку, в которой функция f () не дает ощутимого результата. Это позволяет нулям () видеть NaN и продолжать свою работу.
Альтернатива NaN состоит в том, чтобы вызвать исключение, как только встречается недопустимая операция (также называемая сигналом или ловушкой). Помимо огромных потерь производительности, с которыми вы могли столкнуться, в то время не было никакой гарантии, что процессоры будут поддерживать его аппаратно или ОС / язык будет поддерживать его программно; у каждого была своя уникальная снежинка при работе с плавающей точкой. IEEE решил явно обрабатывать его в программном обеспечении как значения NaN, чтобы он был переносимым на любую ОС или язык программирования. Правильные алгоритмы с плавающей запятой, как правило, корректны во всех реализациях с плавающей запятой, будь то node.js или COBOL (хах).
Теоретически вам не нужно устанавливать конкретные директивы #pragma, устанавливать сумасшедшие флаги компилятора, перехватывать правильные исключения или устанавливать специальные обработчики сигналов, чтобы заставить то, что кажется идентичным алгоритму, действительно работать правильно. К сожалению, некоторые разработчики языка и авторы компиляторов были действительно заняты тем, чтобы максимально использовать эту функцию.
Пожалуйста, ознакомьтесь с информацией об истории IEEE 754 с плавающей запятой. Также этот ответ на аналогичный вопрос, на который ответил член комитета: каково обоснование для всех сравнений, возвращающих false для значений NaN IEEE754?
"Интервью со стариком с плавающей точкой"
"История формата с плавающей точкой IEEE"
Что должен знать каждый компьютерщик об арифметике с плавающей точкой
Что ж, log(-1)
дает NaN
, а также acos(2)
также дает NaN
, Означает ли это, что log(-1) == acos(2)
? Очевидно, нет. Следовательно, совершенно логично, что NaN
не равно себе.
Возвращаясь к этому почти два года спустя, вот "NaN-безопасная" функция сравнения:
function compare(a,b) {
return a == b || (isNaN(a) && isNaN(b));
}
Хорошая собственность: если x == x
возвращает ложь, тогда x
является NaN.
(можно использовать это свойство, чтобы проверить, x
является NaN
или нет.)
Попробуй это:
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 просто дает вам информацию о том, что что-то НЕ, а не что это. Например, если я скажу "яблоко не горилла" и "апельсин не горилла", вы бы пришли к выводу, что "яблоко" == "апельсин"?
На самом деле в математике существует понятие, известное как "единство" ценностей. Эти значения являются расширениями, которые тщательно сконструированы для устранения внешних проблем в системе. Например, вы можете думать о кольце на бесконечности в сложной плоскости как о точке или наборе точек, и некоторые ранее претенциозные проблемы исчезают. Есть и другие примеры этого в отношении количества множеств, где вы можете продемонстрировать, что вы можете выбрать структуру континуума бесконечностей, если |P(A)| > |A| и ничего не ломается.
ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Я работаю только со смутным воспоминанием о некоторых интересных предостережениях во время обучения математике. Я прошу прощения, если я сделал ужасную работу по представлению концепций, на которые я ссылался выше.
Если вы хотите верить, что NaN является единственным значением, то вы, вероятно, будете недовольны некоторыми результатами, такими как оператор равенства не работает так, как вы ожидаете / хотите. Однако, если вы решите верить, что NaN - это скорее континуум "плохости", представленный одиночным заполнителем, то вы совершенно довольны поведением оператора равенства. Другими словами, вы теряете из виду рыбу, которую вы поймали в море, но вы ловите другую, которая выглядит так же, но такая же вонючая.