为啥移位 0 会截断小数点?

Posted

技术标签:

【中文标题】为啥移位 0 会截断小数点?【英文标题】:Why does a shift by 0 truncate the decimal?为什么移位 0 会截断小数点? 【发布时间】:2012-08-20 23:07:35 【问题描述】:

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

【问题讨论】:

另见***.com/questions/3081987/… 在其他语言中,它大多不起作用。 Python、C#、VB.NET、Java、Ruby 都不允许它,其中……几乎所有其他东西。 它在 Perl 中确实有同样的效果。 【参考方案1】:

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

&gt;&gt; 起作用的原因是因为它只对 32 位整数进行操作,因此该值被截断。 (在这种情况下也常用它来代替Math.floor,因为按位运算符的运算符优先级较低,因此可以避免括号混乱。)

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

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

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

另外,它与| 0 做同样的事情,后者更常见。

【讨论】:

您能详细说明为什么&gt;&gt; 有效吗?它是否将类型强制转换为整数? @voithos:是的。一个 32 位整数。 @viothos:再次阅读该段落。按位移位对 32 位整数进行操作,因此必须将其强制转换为 1 才能完成移位。 它不舍入,而是截断。 -1.6 &lt;&lt; 0Math.round(-1.6) 是什么? @voithos: floor 向负无穷方向移动,ceil 向正无穷方向移动,round 循环,“casting”(或 JavaScript 等价物)被截断。【参考方案2】:

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

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

上述语句意味着JavaScript引擎会将&lt;&lt;运算符的两个操作数隐式转换为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

【讨论】:

【参考方案3】:

来自Mozilla documentation of bitwise operators(包括移位运算符)

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

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

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

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

【讨论】:

感谢您提及 Java 和 C#;我只是检查 Python,如果您尝试将浮点数移动一个整数,它也会抛出一个 TypeError【参考方案4】:

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

产生式 ShiftExpression : ShiftExpression >> AdditiveExpression 评估如下:

    设 lref 为计算 ShiftExpression 的结果。 令 lval 为 GetValue(lref)。 设 rref 为 AdditiveExpression 的计算结果。 令 rval 为 GetValue(rref)。 令 lnum 为 ToInt32(lval)。 设 rnum 为 ToUint32(rval)。 令 shiftCount 为屏蔽掉除 rnum 的最低有效 5 位之外的所有结果,即计算 rnum & 0x1F。 返回对 lnum 执行符号扩展右移 shiftCount 位的结果。传播最高有效位。这 结果是一个带符号的 32 位整数。

【讨论】:

【参考方案5】:

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

这是&lt;&lt;左移运算符规范的摘录:

产生式 ShiftExpression : ShiftExpression

    设 lref 为计算 ShiftExpression 的结果。 令 lval 为 GetValue(lref)。 设 rref 为 AdditiveExpression 求值的结果。 令 rval 为 GetValue(rref)。 令 lnum 为 ToInt32(lval)。 设 rnum 为 ToUint32(rval)。 令 shiftCount 为屏蔽掉除 rnum 的最低有效 5 位之外的所有结果,即计算 rnum & 0x1F。 返回将 lnum 左移 shiftCount 位的结果。结果是一个带符号的 32 位整数。

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

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

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

【讨论】:

以上是关于为啥移位 0 会截断小数点?的主要内容,如果未能解决你的问题,请参考以下文章

存储过程中为啥会丢失小数点前面的0

不需要的小数截断

剑道网格数字截断到小数点后 2 位。如何让它尊重用户输入的内容?

Java强制转换为啥没有四舍五入

Java中double类型的变量,计算结果的小数点后为啥会有这么多0?

四舍五入的小数截断小值 - 向上舍入