Почему два разных числа равны в JavaScript?

Я общался с консолью JavaScript, когда я вдруг решил попробовать следующее:

0x100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

Удивительно, но они равны: Strange output

Почему это происходит? У них явно разные числа (даже 0xFFFF...FFFF на одну цифру короче)

Если я добавлю F в 0xFFFF...FF, они больше не равны: 0x100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF Even stranger output

Является ли это ожидаемым поведением?

Ответ 1

Все номера в JavaScript внутренне представлены 64-битными числами с плавающей запятой (см. §4.3.19 спецификации). Это означает, что он может точно представлять каждое целое число от 0 до 9007199254740992 (шестнадцатеричное значение 0x20000000000000). Любые целые числа, превышающие это (или меньше отрицательного аналога), возможно, необходимо округлить до ближайшего приблизительного значения.

Заметим:

9007199254740992 === 9007199254740993
> true

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

9007199254740992 === 9007199254740994
> false

Это то, что вы видите во втором фрагменте, где вы добавляете другую цифру F.

Ответ 2

0x100000000000000 == 0xFFFFFFFFFFFFFF дает true while 0x10000000000000 == 0xFFFFFFFFFFFFF дает false. Так что первый - это "предел", скажем.

Проанализируйте числа: 52 бит для 0xFFFFFFFFFFFFF и один дополнительный бит для 0x10000000000000 во внутреннем представлении.

РЕДАКТИРОВАТЬ. Числа такой величины не представлены длинными целыми числами, а двойными поплавками. Это связано с тем, что превосходят 32-битное представление целочисленного значения. Каждое число в javascript представляется как плавающая точка с двойной точностью IEEE754.

Когда вы представляете IEEE754 Double Precision FP Number, вы получаете:

0111 1111 1111 2222
2222 2222 2222 2222
2222 2222 2222 2222
2222 2222 2222 2222

Где (0) - знаковый бит, (1) биты экспоненты и (2) бит мантиссы.

Если вы сравните в JavaScript 0.5 == 5 * 0.1, вы получите true, даже если эта операция имеет плавающее значение (т.е. вы получите некоторую ошибку). Поэтому Javascript допускает небольшую ошибку в операциях с плавающей запятой, поэтому операции, подобные этому, дают истину, как говорит здравый смысл.

Изменить. Что-то не так я написал о Мантиссе: Да, каждая Мантисса начинается с 1 (говорят, что такая мантисса нормализована), НО, что 1 не сохраняется в нормализованном числе (каждый ненулевой показатель имеет только нормированные числа. mantissas для чисел с показателем 000 0000 0000 не следует этому правилу). Это означает, что каждая нормализованная мантисса имеет 52 явных бита и неявный 1.

Теперь: а как насчет 52 бит? Обратите внимание, что размер 0xFF... имеет длину 52 бит. Это означает, что он будет сохранен как: 0 для знака (положительный), 52 для показателя степени и 52 "1" цифры в мантиссе (см. Окончательную заметку у подножия этого ответа). Поскольку один "1" неявный, мы будем хранить 51 "1" и один "0".

0100 0011 0010 1111
1111 1111 1111 1111
1111 1111 1111 1111
1111 1111 1111 1110

(exponent 1075 corresponds to actual exponent 52)

И другой номер имеет 53 бит: один "1" и 52 "0". Поскольку первый "1" неявный, он будет храниться как:

0100 0011 0100 0000
0000 0000 0000 0000
0000 0000 0000 0000
0000 0000 0000 0000

(exponent 1076 corresponds to actual exponent 53)

Теперь пришло время сравнить значения. Они будут сравнивать в равенстве условий: сначала мы берем знак и экспоненту к сравнению. Если они равны, мы рассматриваем мантиссу.

Здесь сравнивается сравнение, учитывая, что небольшая ошибка переносится как произведение округления. Такой epsilon учитывается (epsilon составляет около = 2 ^ -53), а FP ALU обнаруживает, что относительно эти числа отличаются только таким эпсилон, поэтому они кажутся равными только в этом контексте (часто это происходит не сохраняйте вас, как в случае 0.3 == 0.2 + 0.1, являясь каждым из трех чисел двоично-нерепрезентативным, в отличие от 0.5, который есть, и может терпеть ошибку в отношении 0.1 + 0.4).

Примечание О представлении мантиссы и FP: Мантисса всегда, концептуально, ниже 1. Если вы хотите представить большее число, вы должны представить это с помощью показателя степени. Примеры:

  • 0.5 представлен как 0.5 * 2 ^ 0 (рассмотрим правильное приоритет оператора в математике).
  • 1 представляется не как 1 * 2 ^ 0, так как мантисса всегда меньше 1, поэтому представление будет 0.5 * 2 ^ 1.
  • 65, который имеет двоичное представление как 1000001, будет сохранен как (65/128) * 2 ^ 7.

Эти числа представлены как (помните: первый "1" неявный, так как эти показатели для нормализованных чисел):

0011 1111 1111 0000
... more 0 digits

(exponent 1023 stands for actual exponent 0, mantissa in binary repr. is 0.1, and the first "1" is implicit).

0100 0000 0000 0000
... more 0 digits

(exponent 1024 stands for actual exponent 1, mantissa in binary repr. is 0.1, and the first "1" is implicit).

и

0100 0000 0110 0000
0100 0000 0000 0000

(exponent 1030 stands for actual exponent 7, mantissa in binary repr. is 0.1000001, and since the first "1" is implicit, it is stored as 0000 0100 0000...)

Примечание О экспоненте: более низкая прецессия может быть достигнута путем предоставления отрицательных показателей: Показатели кажутся положительными - не совпадают, но реальность такова, что вы должны вычесть 1023 (называемый "смещением" ) к этому число для получения фактического показателя (это означает, что показатель "1" фактически соответствует 2 ^ (- 1022)). Переведя это на 10-уровневую мощность, самый низкий показатель составляет -308 для десятичных чисел (учитывая также возможность мантиссы, как я покажу позже). Наименьшее положительное число:

0000 0000 0000 0000
0000 0000 0000 0000
0000 0000 0000 0000
0000 0000 0000 0001

который: (1 * 2^-52) * 2^-1023 является первым -52, данным мантиссой и -1023 показателем. Последний из них: 1 * 2 ^ (- 1075), который идет в направлении 10 ^ -308, всегда сказано.

Самый низкий показатель 000 0000 0000 соответствует (-1023). Там правило: каждая мантисса должна начинаться с (неявного) "1" или иметь этот показатель. С другой стороны, наивысший показатель может быть 111 1111 1111, но этот показатель зарезервирован для специальных псевдонимов:

0111 1111 1111 0000
0000 0000 0000 0000
0000 0000 0000 0000
0000 0000 0000 0000

соответствует + бесконечность, а:

1111 1111 1111 0000
0000 0000 0000 0000
0000 0000 0000 0000
0000 0000 0000 0000

соответствует -Infinity, и любой шаблон с ненулевой мантиссой, например:

?111 1111 1111 0000
0000 0000 0000 0000
0000 0000 0000 0000
0000 0000 0000 0001

соответствуют NaN (не число, идеальное для представления таких вещей, как log (-1) или 0/0). На самом деле я не уверен, какие мантиссы используются для NaN (либо тихий, либо сигнализирующий NaN). Значок вопроса обозначает любой бит.

Ответ 3

Следующее шестнадцатеричное число:

0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

хранится как стандартное значение поплавка IEEE 754:

1.3407807929942597e+154

Вы добавляете 1 к этому номеру, и оно становится:

0x100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

который также сохраняется как:

1.3407807929942597e+154

Оба числа лежат вне диапазона чисел, которые могут быть точно представлены номером JavaScript (ref). В приведенном выше примере оба числа заканчиваются тем же внутренним представлением, следовательно, они равны, равны.

Напоминание: не следует сравнивать числа с плавающей запятой, используя оператор равенства (ref).

Ответ 4

Это, очевидно, переполнение или округление. Выработайте математически величину чисел и проверите против наибольшего числа.