为什么 0 的偏移会截断小数

Why does a shift by 0 truncate the decimal?

本文关键字:小数 为什么      更新时间:2023-09-26

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

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

>>工作的原因是因为它只对 32 位整数进行操作,因此该值被截断。(在此类情况下也经常使用它而不是Math.floor因为按位运算符的优先级较低,因此可以避免括号的混乱。

由于它只对 32 位整数进行操作,因此它也相当于舍入后带有 0xffffffff 的掩码。所以:

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

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

此外,它与 | 0 执行相同的操作,后者更常见。

Math.random()返回一个介于 0(含(和 1(不包括(之间的数字。将此数字乘以整数得到具有小数部分的数字。<<运算符是消除小数部分的快捷方式:

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

上述语句意味着 JavaScript 引擎将隐式地将运算符<<的两个操作数转换为 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 位而做的唯一重要事情。哎呀。

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

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

根据 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 成为屏蔽除最低有效 5 位之外的所有 rnum 的结果,即计算 rnum & 0x1F。
  8. 返回通过 shiftCount 位执行 lnum 的符号扩展右移的结果。传播最高有效位。这 结果是一个有符号的 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 成为屏蔽除最低有效 5 位之外的所有 rnum 的结果,即计算 rnum & 0x1F。
  8. 返回左移 lnum 按 shiftCount 位的结果。结果是一个有符号的 32 位整数。

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

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

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