为什么移位 0 会截断小数?

IT技术 javascript numbers operators bitwise-operators bit-shift
2021-02-15 00:18:46

我最近发现了这段 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 的细微差别,还是在其他语言中也存在?我以为我了解位移位,但这让我感到困惑。

5个回答

你是对的;它用于截断值。

之所以>>有效是因为它仅对 32 位整数进行操作,因此该值被截断。(它也常用于此类情况,而不是Math.floor因为按位运算符的运算符优先级较低,因此您可以避免出现一堆括号。)

并且由于它只对 32 位整数进行运算,因此它也相当于一个带有0xffffffff后舍入的掩码所以:

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

但这不是预期行为的一部分,因为Math.random()将返回 0 到 1 之间的值。

此外,它与| 0更常见的做同样的事情

你能详细说明为什么>>有效吗?它是否将类型强制转换为整数?
2021-04-15 00:18:46
@voithos:是的。一个 32 位整数。
2021-04-15 00:18:46
@voithos:floor向负无穷大移动,ceil向正无穷大移动,round轮次,“转换”(或其 JavaScript 等效项)截断。
2021-04-17 00:18:46
它不圆,它截断。什么是-1.6 << 0Math.round(-1.6)
2021-04-26 00:18:46
@viothos:再次阅读该段落。按位移位操作 32 位整数,因此必须将其强制为 1 才能完成移位。
2021-05-11 00:18:46

Math.random()返回 0(包含)和 1(不包含)之间的数字。将此数字与整数相乘会得到一个带有小数部分的数字。<<操作员是用于消除小数部分的快捷方式:

所有按位运算符的操作数都转换为大端顺序和二进制补码格式的有符号 32 位整数。

上述语句意味着 JavaScript 引擎会将<<operator 的两个操作数隐式转换为 32 位整数;对于数字,它通过切掉小数部分来实现(不适合 32 位整数范围的数字比小数部分更松散)。

它只是 JavaScript 的细微差别,还是在其他语言中也存在?

您会注意到松散类型语言中的类似行为。以 PHP 为例:

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

对于强类型语言,程序通常会拒绝编译。C# 抱怨如下:

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

对于这些语言,您已经有了类型转换运算符:

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

来自按位运算符(包括移位运算符)Mozilla 文档

所有按位运算符的操作数都转换为大端顺序和二进制补码格式的有符号 32 位整数。

所以基本上代码使用移位运算符的那个有点偶然的方面作为由于移位 0 位而做的唯一重要的事情oop。

它只是 JavaScript 的细微差别,还是在其他语言中也存在?

我当然不能说所有语言,但 Java 和 C# 都不允许将double值作为左操作数和移位运算符。

感谢您提及 Java 和 C#;我只是检查 Python,TypeError如果您尝试通过 int 移动浮点数,它也会抛出 a
2021-05-07 00:18:46

根据 ECMAScript 语言规范:http :
//ecma-international.org/ecma-262/5.1/#sec-11.7.1

产生式 ShiftExpression : ShiftExpression >> AdditiveExpression 计算如下:

  1. 令 lref 为评估 ShiftExpression 的结果。
  2. 令 lval 为 GetValue(lref)。
  3. 让 rref 是对 AdditiveExpression 求值的结果。
  4. 令 rval 为 GetValue(rref)。
  5. 令 lnum 为 ToInt32(lval)。
  6. 令 rnum 为 ToUint32(rval)。
  7. 令 shiftCount 是屏蔽掉 rnum 的除最低有效 5 位之外的所有位的结果,即计算 rnum & 0x1F。
  8. 返回对 lnum 执行符号扩展右移 shiftCount 位的结果。传播最高有效位。结果是一个有符号的 32 位整数。

您观察到的行为在ECMA-262 标准中定义

这是<<左移运算符规范的摘录

产生式 ShiftExpression : ShiftExpression << AdditiveExpression 计算如下:

  1. 令 lref 为评估 ShiftExpression 的结果。
  2. 令 lval 为 GetValue(lref)。
  3. 让 rref 是对 AdditiveExpression 求值的结果。
  4. 令 rval 为 GetValue(rref)。
  5. 令 lnum 为 ToInt32(lval)。
  6. 令 rnum 为 ToUint32(rval)。
  7. 令 shiftCount 是屏蔽掉 rnum 的除最低有效 5 位之外的所有位的结果,即计算 rnum & 0x1F。
  8. 返回 lnum 左移 shiftCount 位的结果。结果是一个有符号的 32 位整数。

如您所见,两个操作数都被转换为 32 位整数。因此小数部分消失了。

这同样适用于其他位移运算符。您可以在我链接到的文档的11.7 Bitwise Shift Operators部分中找到它们各自的描述

在这种情况下,执行移位的唯一效果是类型转换。Math.random()返回一个浮点值。