Frontender Magazine

NaN это не «не число»

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

Один из терминов, который очень часто используют неправильно,— это NaN.

Его корни лежат в стандарте IEEE754, он определяет NaN как как специальное значение (на самом деле, как мы увидим далее, множество значений), которое используется если результат вычисления не может или не должен быть представлен как конкретное число, или попросту неизвестен. Например: asin(2) или 0/0. Это приводит нас к следующему открытию: NaN — на самом деле значение числового типа! Просто это специальное число, которое представлено по-особенному.

Итак, NaN, согласно стандарту IEEE754, не какое-то значение не числового типа, но в действительности — число. А это значит, что не совсем корректно говорить, что строка "foo" является NaN, ибо на самом деле это не так.

Всё становится ещё более интересным если мы посмотрим, как значение NaN представлено внутри. У чисел с плавающей запятой двойной точности (64 бит), которые используются для представления значений типа Number в JavaScript и некоторых других языках, специальный диапазон значений, у которых экспонента равна значению 0x7FF (11 бит), выделен для представления числа NaN. А это значит, что оставшиеся 53 бита могут принимать произвольные значения (исключая значения с мантиссой равной нулю, ибо эти значения зарезервированы для двух других специальных значений: +Infinity и -Infinity). Итого, для чисел с плавающей запятой двойной точности мы можем сконструировать значение NaN 9007199254740990 (2^53 - 3) разными способами. Это довольно-таки много.

Подводя итог: если вы используете термин NaN как синоним, обозначающий «что угодно, что не является числом» (строка, значение null, объект, и т.д.), пожалуйста, не делайте этого больше. А если нет — пожалуйста, распространите это знание дальше и помогите другим людям быть такими же точными, как и вы.

Материалы для дальнейшего изучения:

Если вы заметили ошибку, вы всегда можете отредактировать статью, создать issue или просто написать об этом Антону Немцеву в skype ravencry.

Иван Курносов

Комментарии (21 комментарий, если быть точным)

Автар пользователя
SilentImp

@zerkms я не могу не заметить, что согласно спецификации ECMAScript NaN расшифровывается именно как «Not-a-Number». Что касается IEEE 754-2008 — даже не знаю. По славной традиции IEEE хочет денег за каждый пук, в том числе за доступ к спецификации.

А какое практическое применение ты видишь? Хорошо понимать что такое NaN, но не менее важно понимать как это понимание повлияет на его практическое использование. Можешь привести примеры «good» и «bad practice» использования NaN?

Автар пользователя
zerkms

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

Автар пользователя
SilentImp

@zerkms а как тогда проверять является ли аргумент числом? И можно конкретный пример проверки, который можно запустить в консоли браузера и получить неожиданный результат?

Автар пользователя
zerkms

А ты придумай задачу, и мы придумаем её решение :-)

js isNaN(true); // false isNaN(42); // false isNaN('42'); // false

Автар пользователя
SilentImp

@zerkms ты забрал ajax некий json. Распарсил. Нужно проверить является ли значение некого свойство объекта числом или нет.

При этом я допускаю что его значение может быть '42'. Тогда, насколько я могу судить, надо проверить что это число, если нет — проверить строка ли это, если строка, то распарсить её с помощью parseInt(prop, 10) и проверить получили ли мы число.

Реалистичная задача?

Автар пользователя
zerkms

Почти. Результат parseInt нужно проверять не с позиции "число ли у нас", а с позиции "не вернулся ли нам NaN".

Тогда, да

js const parsed = parseInt(prop, 10); const itIsGood = !Number.isNaN(parsed);

Автар пользователя
SilentImp

@zerkms это ты вторую половину задачи рассмотрел, а как изначально проверить является значение свойства числом? Не сразу же в parseInt пихать… Думаю проверки всё же подешевле будут.

Автар пользователя
zerkms

Думаю проверки всё же подешевле будут.

Например?

Автар пользователя
SilentImp

@zerkms Я бы сделал это как то так:

if(isNaN(obj.test_prop)) { if (typeof obj.test_prop === "string") { obj.test_prop = parseInt(obj.test_prop ,10); if(isNaN(obj.test_prop)) throw new Error("мы все умрем!"); } else { throw new Error("мы все умрем!"); } } console.log("в obj.test_prop совершенно точно — число");

Но на фоне статьи не уверен насколько это правильно.

Автар пользователя
zerkms

Это вообще неправильно - я же выше показал, что булевы возвращают false. Т.е. если с obj.test_prop лежит, к примеру, true, то ты выведешь в obj.test_prop совершенно точно — число.

Автар пользователя
SilentImp

@zerkms ага, я это уже понял. А правильно то как?

Автар пользователя
zerkms
  1. typeof
  2. После этого - parseInt если там строка (но тут тоже куча нюансов, ибо "42asd" parseInt превратит в 42)
Автар пользователя
SilentImp

@zerkms с typeof, если честно, нюансов тоже более чем достаточно =^__^=

Автар пользователя
zerkms

Вообще никаких - json умеет только числа, строки, булевы, объекты и null. У тебя или число-число, или чтоугодновообще :-)

Автар пользователя
GreLI

isNaN(true); // false isNaN(42); // false isNaN('42'); // false

— не вижу ничего удивительного, все это значения приводятся к числу (true к 1, '42' к 42). Читайте «You don't know JS» — там всё написано.

Автар пользователя
zerkms

@GreLI я тоже не вижу ничего удивительного, это был пример того, что люди, обычно, от этой функции не ожидают.

Ну и да, про это лучше стандарт почитать всё-таки :-)

Автар пользователя
dagolinuxoid

@zerkms да, у NaN (not a number) просто не очень удачное название, интересно а как бы вы обозвали?)

Автар пользователя
ElForastero

@dagolinuxold, Uncaught exception.

Автар пользователя
junkiemonkey

А как же насчет Object.prototype.toString.call(5).split(' ').pop().slice(0, -1) == "Number" ?

Автар пользователя
zerkms

@dagolinuxoid хорошее название у него, имхо.

@ElForastero ну в IEEE754 нельзя термин "исключение" использовать, он в рамках его не определён.

@junkiemonkey для чего именно? o_O

Автар пользователя
rantiev

согласен с zerkms, мне даже в голову не приходило использовать isNaN для типа переменной. из названия функции следует то, для чего она должна использоваться, проверять является ли значение NaN. Какие проблемы проверить так "typeof 4" ? Вроде там приведения не будет и number получится только если там число. если нет проверяй строка ли да parseInt потом.