Да, не-число не равно самому себе. Но в отличие от случая с undefined и null, когда сравнение неопределенного значения с null истинно, но жесткая проверка (===) того же даст вам ложное значение, поведение NaN обусловлено спецификацией IEEE, которой все системы должны придерживаться . В этом блоге я расскажу о NaN и о том, как они реализованы в движке v8.

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

Что такое NaN?

В соответствии со стандартами IEEE любое значение, в котором все биты экспоненты установлены на 1 и хотя бы один бит дроби / мантиссы установлен как ненулевое, представляет собой NaN.

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

В научном представлении любого числа число имеет знак (+/-), мантиссу и показатель степени. Например,

Число 1,234 с основанием 10 в научном представлении может быть записано как 1234 * 10 ^ -3, где 1234 - мантисса / мантисса, а 10 ^ -3 - показатель степени.

IEEE предлагает использовать аналогичное представление для сохранения чисел в двоичном формате, для значения с плавающей запятой одинарной точности формат IEEE задается как

| sign bit | exponent | mantissa |
 where,
  sign bit = 0 | 1
  exponent = -127 to +128
  mantissa = 24 bits of significand value

Числа могут быть записаны в 2-х форматах в соответствии с приведенным выше представлением.

Single: SEEEEEEE EMMMMMMM MMMMMMMM MMMMMMMM
Double: SEEEEEEE EEEEMMMM MMMMMMMM MMMMMMMM MMMMMMMM MMMMMMMM MMMMMMMM MMMMMMMM

Таким образом, значение NaN в соответствии с определением (все экспоненты установлены на 1 и установлен хотя бы один дробный бит) можно представить как:

| 0 | 1111111 | 00...01
or
| 0 | 1111111 | 10...00

Совет: для зарезервированной категории используются значения экспоненты -127 и +128, NaN - +128.

Типы NaN

Есть 2 типа NaN, один может говорить, другой не умеет.

Тихий NaN

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

Сигнализация NaN

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

Реализация NaN

Реализация NaN и различие между тихим и сигнальным NaN часто остается в базовой системе, помощник std :: numeric_limits обычно используется в C ++ для получения значения NaN, если вы углубитесь в файлы заголовков std, вы обнаружите, что реализация по умолчанию спрятана глубоко внутри файла math.h, который, в свою очередь, использует встроенные функции limits.h который выглядит так

// Quiet NaN
// float NaN
static const unsigned int _QNAN_F = 0x7fc00000;
// long double NaN
static const unsigned int _QNAN_LDBL128[4] = {0x7ff80000, 0x0, 0x0, 0x0};
// Signaling NaN
// float NaN
static const unsigned int _SNAN_F= 0x7f855555;
// double NaN
static const unsigned int _SNAN_D[2] = {0x7ff55555, 0x55555555};
// long double NaN
static const unsigned int _SNAN_LDBL128[4] = {0x7ff55555, 0x55555555, 0x0, 0x0};

Различие между несколькими типами NaN осуществляется на основе их замаскированных битов. Если вы внимательно посмотрите на приведенный выше код, вы обнаружите, что все значения соответствуют начальному определению NaN, как определено IEEE 754.

NaN в версии 8

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

v8 также использует хелперы std :: numeric_limits C ++ для определения значений NaN, однако v8 также имеет свои собственные специальные хелперы NaN, которые работают аналогичным образом. В v8 значение NaN можно встретить на двух уровнях: один - во время компиляции, а другой - во время выполнения, давайте посмотрим на оба.

NaN времени компиляции

Каждый раз, когда компилятор v8 встречает постоянное значение NaN во время компиляции / создания графа, он автоматически присваивает ему значение тихого NaN.

NaN времени выполнения

Во время выполнения тихое значение NaN может быть возвращено parseInt и parseFloat, или они могут быть возвращены любой математической операцией или проверкой MIPS.

Все переменные в v8 являются типизированными, есть 2 класса Typer, которые выполняют задачу набора переменных, один принадлежит TurboFan, а другой - ASMTyper, для понимания набора текста и потока компилятора v8 вам может понравиться Benedikt слайды .

Именно эти типизаторы отвечают за проверку типов ваших переменных перед их оценкой, поэтому при вызове компаратора для значения NaN вы в конечном итоге активируете this в TurboFan.

Если любое из значений RHS или LHS равно NaN, компаратор всегда будет возвращать false, что является правильным с учетом спецификации IEEE. По умолчанию NaN в JS - это тихие NaN, поэтому, если будущий компилятор TurboFan захочет добавить сигнальные функции NaN, он просто проверит наличие замаскированных битов, а затем вызовет ошибку.

О, мы закончили? Блин!

Да, ребята, вот и все, погрузитесь в маленький мир наших NaN, теперь они чувствуют себя такими особенными.

И подождите, вы можете когда-нибудь получить значение NaN, равное 0,0000, если вы скомпилируете свой код C ++ с флагом -ffast-math на процессорах Intel.

(╯°□°)╯︵ ┻━┻