Почему сдвиг на 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, или это происходит на других языках? Я думал, что понял бит-сдвиг, но это меня озадачило.
Ответы
Ответ 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()
возвращает значение с плавающей запятой.