Это весело! Итак, SpiderMonkey внутренне использует представление теговых значений для представления «нетипизированных значений» JavScript - это позволяет виртуальной машине определять такие вещи, как «переменная, хранящаяся в a
, представляет собой число, а значение, хранящееся в b
, представляет собой число, так работает a + b
делает числовое сложение ".
Существует множество различных схем для маркировки значений, и SpiderMonkey использует такую схему, которая называется "NaN boxing". Это означает, что все нетипизированные значения в движке представлены 64-битными значениями, которые могут быть:
- двойной или
- помеченный non-double, который живет в "пространстве NaN" значений с плавающей запятой IEEE двойной точности.
Настоящая хитрость в том, что современные системы обычно используют однобитовый шаблон для представления NaN, который вы можете наблюдать в результате математических операций sqrt(-1)
или log(0)
. но есть много битовых комбинаций, которые также считаются NaN согласно спецификации IEEE с плавающей запятой.
Двойник состоит из подполей:
{sign: 1, exponent: 11, significand: 52}
NaN представляются заполнением поля экспоненты 1с и помещением ненулевого значения в значении.
Если вы запускаете небольшую программу, подобную этой, чтобы увидеть значения NaN вашей платформы:
#include <stdio.h>
#include <math.h>
#include <limits>
static unsigned long long
DoubleAsULL(double d) {
return *((unsigned long long *) &d);
}
int main() {
double sqrtNaN = sqrt(-1);
printf("%5f 0x%llx\n", sqrtNaN, DoubleAsULL(sqrtNaN));
double logNaN = log(-1);
printf("%5f 0x%llx\n", logNaN, DoubleAsULL(logNaN));
double compilerNaN = NAN;
printf("%5f 0x%llx\n", compilerNaN, DoubleAsULL(compilerNaN));
double compilerSNAN = std::numeric_limits<double>::signaling_NaN();
printf("%5f 0x%llx\n", compilerSNAN, DoubleAsULL(compilerSNAN));
return 0;
}
Вы увидите такой вывод:
-nan 0xfff8000000000000 // Canonical qNaNs...
nan 0x7ff8000000000000
nan 0x7ff8000000000000
nan 0x7ff4000000000000 // sNaN (signaling)
Обратите внимание, что единственное различие для тихих NaN заключается в знаковом бите, за которым всегда следуют 12 битов 1 с, что удовлетворяет требованию NaN, указанному выше. Последний, сигнальный NaN, очищает 12-й (is_quiet) бит NaN и позволяет 13-му сохранять инвариант NaN, упомянутый выше.
Кроме этого, пространство NaN свободно играть - 11 бит для заполнения показателя степени, убедитесь, что знак отличен от нуля, и у вас осталось много места. В x64 мы используем предположение о 47-битном виртуальном адресе, что оставляет нам 64 - 47 - 11 = 6
битов для аннотирования типов значений. На x86 все указатели объектов помещаются в младшие 32 бита.
Однако нам все еще нужно убедиться, что неканонические NaN, если они проникают через что-то вроде js-ctypes, не производят что-то похожее на помеченные не двойные значения, потому что это может привести к эксплуатируемому поведению в ВМ. (Относиться к числам как к объектам - очень-очень плохая новость.) Поэтому, когда мы формируем двойные числа (как в DOUBLE_TO_JSVAL), мы обязательно канонизируем все двойные числа, где d != d
, в каноническую форму NaN.
Больше информации в ошибка 584168 .