Подтвердить что ты не робот

Почему сдвиг на 0 усекает десятичное число?

Недавно я нашел этот фрагмент кода JavaScript:

Math.random() * 0x1000000 << 0

Я понял, что первая часть просто генерирует случайное число между 0 и 0x1000000 (== 16777216).

Но вторая часть показалась странной. Какой смысл выполнять бит-сдвиг на 0? Я не думал, что все будет. Однако при дальнейшем исследовании я заметил, что сдвиг на 0, казалось, усекал десятичную часть числа. Кроме того, это не имело значения, было ли это сдвиг вправо, или сдвиг влево, или даже беззнаковый сдвиг вправо.

> 10.12345 << 0
10
> 10.12345 >> 0
10
> 10.12345 >>> 0
10

Я тестировал как с Firefox, так и с Chrome, и все одинаково. Итак, в чем причина этого наблюдения? И это просто нюанс JavaScript, или это происходит на других языках? Я думал, что понял бит-сдвиг, но это меня озадачило.

4b9b3361

Ответ 1

Вы правы; он используется для усечения значения.

Причина >> заключается в том, что она работает только с 32-битными целыми числами, поэтому значение усекается. (Он также обычно используется в таких случаях вместо Math.floor, потому что побитовые операторы имеют низкий приоритет оператора, поэтому вы можете избежать беспорядка круглых скобок.)

И поскольку он работает только с 32-битными целыми числами, он также эквивалентен маске с 0xffffffff после округления. Итак:

0x110000000      // 4563402752
0x110000000 >> 0 // 268435456
0x010000000      // 268435456

Но это не часть предполагаемого поведения, поскольку Math.random() вернет значение от 0 до 1.

Кроме того, он делает то же самое, что и | 0, что является более распространенным.

Ответ 2

Math.random() возвращает число от 0 (включительно) и 1 (исключение). Умножение этого числа на целое число приводит к числу, которое имеет десятичную часть. Оператор << является ярлыком для удаления десятичной части:

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

Вышеприведенные утверждения означают, что механизм JavaScript будет неявно преобразовывать оба операнда оператора << в 32-битные целые числа; для чисел, которые он делает, путем измельчения дробной части (числа, которые не соответствуют 32-битовому целочисленному диапазону, больше, чем просто десятичная часть).

И это просто нюанс JavaScript, или это происходит в других языки?

Вы заметите подобное поведение на слабо типизированных языках. Например, PHP:

var_dump(1234.56789 << 0);
// int(1234)

Для сильно типов языков программы обычно отказываются компилировать. С# жалуется так:

Console.Write(1234.56789 << 0);
// error CS0019: Operator '<<' cannot be applied to operands of type 'double' and 'int'

Для этих языков у вас уже есть операторы ввода типов:

Console.Write((int)1234.56789);
// 1234

Ответ 3

Из документации по побитовым операторам Mozilla (которая включает операторы сдвига)

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

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

И это просто нюанс JavaScript, или это происходит на других языках?

Я не могу говорить для всех языков, конечно, но ни Java, ни С# не позволяют значениям double быть левым операндом оператора сдвига.

Ответ 4

Согласно спецификации языка ECMAScript:
http://ecma-international.org/ecma-262/5.1/#sec-11.7.1

Производство ShiftExpression: ShiftExpression → AdditiveExpression оценивается следующим образом:

  • Пусть lref является результатом вычисления выражения Shift.
  • Пусть lval - GetValue (lref).
  • Пусть rref является результатом оценки AdditiveExpression.
  • Пусть rval - GetValue (rref).
  • Пусть lnum - ToInt32 (lval).
  • Пусть rnum - ToUint32 (rval).
  • Пусть shiftCount является результатом маскировки всех, кроме наименее значимых 5 бит rnum, то есть вычисления rnum и 0x1F.
  • Возвращает результат выполнения сдвига вправо сдвига lnum по битам shiftCount. Самый старший бит размножается. результатом является подписанное 32-разрядное целое число.

Ответ 5

Поведение, которое вы наблюдаете, определено в стандарте ECMA-262

Здесь выдержка из спецификации оператора сдвига <<:

Произведение ShiftExpression: ShiftExpression < < АддитивноеВыражение оценивается следующим образом:

  • Пусть lref является результатом вычисления выражения Shift.
  • Пусть lval - GetValue (lref).
  • Пусть rref является результатом оценки AdditiveExpression.
  • Пусть rval - GetValue (rref).
  • Пусть lnum - ToInt32 (lval).
  • Пусть rnum - ToUint32 (rval).
  • Пусть shiftCount является результатом маскировки всех, кроме наименее значимых 5 бит rnum, то есть вычисления rnum и 0x1F.
  • Возвращает результат сдвига левой строки lnum по битам shiftCount. Результатом является подписанное 32-разрядное целое число.

Как вы можете видеть, оба операнда передаются в 32 битные целые числа. Следовательно, исчезновение десятичных частей.

То же самое относится и к другим операторам сдвига бит. Вы можете найти их соответствующие описания в разделе 11.7 Операторы побитового смены документа, с которым я связан.

В этом случае единственным эффектом выполнения сдвига является преобразование типа. Math.random() возвращает значение с плавающей запятой.