Почему:Math.floor(2e+21)!= ~~(2e+21)

Я не эксперт в побитовых операторах, но я часто вижу шаблон, который используется программистами из 256 тыс. Демонстраций на соревнованиях. Вместо того, чтобы использовать Math.floor() функция, двойной побитовый оператор НЕ используется ~~ (может быстрее?).

Как это:

Math.floor(2.1); // 2
~~2.1 // 2

Поиск выявил, что есть больше шаблонов, которые используются таким же образом:

2.1 | 0  // 2
2.1 >> 0 // 2

Играя с этим в консоли разработчика, я заметил поведение, которое я не уверен, что полностью понимаю.

Math.floor(2e+21);  // 2e+21
~~2e+21;    // -1119879168
2e+21 | 0;  // -1119879168

Что происходит под капотом?

2 ответа

Решение

Как отметил Феликс Кинг, числа конвертируются в 32-битные целые числа со знаком. 2e9 меньше максимального положительного значения int со знаком, так что это работает:

~~(2e9)  //2000000000

Но когда вы переходите к 2e10, он не может использовать все биты, поэтому он просто берет младшие 32 бита и преобразует их в int:

~~(2e10) //-1474836480

Вы можете проверить это, используя другой побитовый оператор и убедившись, что он захватывает младшие 32 бита:

2e10 & 0xFFFFFFFF // also -1474836480
~~(2e10 & 0xFFFFFFFF) // also -1474836480

Math.floor создан для учета больших чисел, поэтому, если важна точность в большом диапазоне, вам следует ее использовать.

Также обратите внимание: ~~ выполняет усечение, которое аналогично настилу только для положительных чисел. Это не будет работать для негативов:

Math.floor(-2.1) // -3
~~(-2.1) // -2

Как указано в Документах MDN, и здесь я цитирую,

Операнды всех побитовых операторов преобразуются в 32-разрядные целые числа со знаком в формате дополнения до двух.

Это означает, что когда вы применяете побитовый оператор, например ~, чтобы 2.1 сначала он преобразуется в целое число, и только затем применяется оператор. Это эффективно обеспечивает эффект округления (пол) для положительных чисел.

Что касается того, почему используются эти операторы, вместо того, чтобы гораздо лучше понять Math.floor, Есть две основные причины. С одной стороны, эти операторы могут быть значительно быстрее, чтобы достичь того же результата. Помимо производительности, некоторые люди просто хотят получить максимально короткий код. Все три упомянутых вами оператора достигают того же эффекта, но ~~ просто самый короткий и, пожалуй, самый простой для запоминания.

Учитывая, что преобразование с плавающей точкой в ​​целое происходит до применения побитовых операторов, давайте посмотрим, что происходит с ~~, Я представлю наше целевое число (2, после преобразования из 2.1), используя 8 бит вместо 32 для краткости.

  2: 0000 0010
 ~2: 1111 1101    (-3)
~~2: 0000 0010

Итак, вы видите, мы применяем оператор для извлечения только целочисленной части, но мы не можем применить только одну побитовую, потому что это испортит результат. Мы возвращаем его к желаемому значению, применяя второй оператор.


Что касается вашего последнего примера, примите во внимание, что число, с которым вы тестируете, 2e+21, является относительно большим числом. За 2 следует двадцать один ноль. Он просто не подходит как 32-разрядное целое число (тип данных, в который он преобразуется, когда вы применяете побитовые операторы). Просто посмотрите на разницу между вашим числом и тем, что может представлять 32-разрядное целое число со знаком.

Max. Integer:             2147483647
       2e+21: 2000000000000000000000

Как насчет двоичного?

Max. Integer:                                        01111111111111111111111111111111
       2e+21: 11011000110101110010011010110111000101110111101010000000000000000000000

Довольно большой, а?

Что действительно происходит под капотом, так это то, что Javascript усекает ваше большое число до того, что оно может представлять в 32 битах.

110110001101011100100110101101110001011 10111101010000000000000000000000
^----            Junk             ----^

Когда мы преобразуем наше усеченное число в десятичное, мы возвращаем то, что вы видите.

Bin: 10111101010000000000000000000000
Dec: -1119879168

Наоборот, Math.floor учитывает большие числа и избегает их усекания, что является одной из возможных причин того, что он медленнее, хотя и точен.

Другие вопросы по тегам