在JavaScript中,'x << ~y'代表什么?

56

'x << ~y' 在JavaScript中代表什么意思?

我了解位运算符SHIFT的作用:

x << y 等同于 x * 2y

而波浪线符号~则表示:

~x AS -(x+1)

所以,我假设如下:

5 << ~3 AS 5 * 2-4 或 5 * Math.pow(2, -4)

它应该得出 0.3125

但是,当我运行 5 << ~3 时,结果为 1342177280

这种操作组合为什么会产生 1342177280 而不是 0.3125?请详细说明步骤和原因。

(这个问题类似于 Stack Overflow 的问题 What are bitwise operators?,问的是位操作符中的移动操作符 SHIFT。)


7
位移操作如何能够生成类似于0.3125这样的小数结果? - edc65
9
!--<<~ 之后是什么?我应该发布“^<<!~ 操作符是什么?” - dhein
4
@Zaibis !~--this[](...[this[$]],_=>..._)这段代码中使用了一些JavaScript语法,其中包含一些操作数组和函数的元素。更具体地说,该代码对数组进行索引并调用一个匿名函数,然后对结果进行减一和按位取反的操作。最后,返回的结果将被转换为布尔值,并取其逻辑非。由于缺少上下文信息,无法准确判断此代码的目的和功能。 - azz
2
@Zaibis,你可能只需要将左移操作符和右移操作符互换一下,然后再问这个问题。也许你会得到20个赞。 - Jonny Henly
1
@JonnyHenly:改变左移和右移...但是....那将会是....>>~它看起来像一个人.....他可能在做什么呢....我觉得我有责任问SO“这个人操作符是做什么的?”x'D - dhein
显示剩余4条评论
5个回答

58

x << -n等同于x << (32 - n)
~3 == -4,所以
5 << ~3 === 5 << (32 - 4) === 5 << 28,结果为1,342,177,280

准确来说,X << -n并不等同于X << (32 - n)……实际上这既简单又复杂……位移运算符的有效范围是0到31……位移运算符中的RHS首先被转换为无符号32位整数,然后与31(十六进制1f)(二进制11111)进行掩码处理。

                   3 = 00000000000000000000000000000011  
                  ~3 = 11111111111111111111111111111100
       0x1f (the mask) 00000000000000000000000000011111
                       --------------------------------
            ~3 & 0x1f  00000000000000000000000000011100 = 28

当数量小于32时,它与我上面发布的完全相同。
位运算使用32位整数。负位移是无意义的,因此被包装成正的32位整数。 << operator的工作原理
rhs转换为无符号32位整数-如此处所述 ToUInt32 ToUint32基本上将一个数字返回模2 ^ 32的数字。

2
你怎么得出“-n”等于“(32-n)”这个引用的?我并不怀疑这是正确答案,但我只是好奇因为我找不到答案。 - choz
6
实际上,这一点可以追溯到计算机体系结构。这是因为大多数计算机指令集用5位来存储移位量(至少对于MIPS而言)。特别地,这些位是无符号二进制的。 - Spencer Wieczorek
不是32-n。它只使用最低有效的5位,即n&31(有关运算符<<,请参见引用的参考文献12.8.3.11子点11)。如果n为负,则32 + n == n&31 - edc65
2
@choz ... 当 abs(n) <= X 时,-n modulo X 等于 X - n ... 这只是我过于简化的表达。 - Jaromanda X
如果+3是(二进制)....0011,那么它的补码应该是....1101而不是....1100,其中省略号将最高有效位扩展到需要的最左边。哎呀!这个注释是多余或错误或两者都有,但我无法删除它,抱歉。 - Harry Weston
~ 是按位取反(或一补数)... 二补数则完全不同。 - Jaromanda X

22

~运算符会翻转项目的位,而<<是位左移操作。以下是二进制逐步进行的过程。请注意,最左边的位为1表示负数,这种格式是二进制补码

3         // (00000000000000000000000000000011 => +3 in decimal)
// ~ flips the bits
~3        // (11111111111111111111111111111100 => -4 in decimal)
// The number 5 (..00101) shifted by left by -4 (-4 unsigned -> 28)
5         // (00000000000000000000000000000101 => +5 in decimal)
5 << -4   // (01010000000000000000000000000000 => +1342177280 in decimal)
在最后一行中,位数被移位并“旋转”到另一侧,导致一个大正数。实际上,通过负数进行移位类似于按位旋转(溢出的位被旋转到另一侧),而通过正数进行移位则没有这样的行为。缺点是未旋转的位被忽略了,这意味着 5 << -4 相当于执行 5 << (32 - 4),实际上旋转只是一个大的移位。
原因在于位移只是一个 5 位无符号整数。因此,在二进制补码中,补码表示的负数-4 (11100) 转换成无符号整数就是28

@JaromandaX 哦,你是对的,这是二进制5向右移动4位。等我一下,这解释了为什么我差了1。 - Spencer Wieczorek
@choz,那是我的失误,我以错误的顺序读取了最后一个表达式,偶然得到了一个非常接近的数字。我已经更新了我的答案。 - Spencer Wieczorek
1
可能需要添加免责声明或者提醒一下,JavaScript会将数字从64位双精度浮点数转换为32位整数,进行位操作,然后再转换回来。不过这是一个好的答案,现在已经修复了。 - Jonny Henly
@SpencerWieczorek 我在想,因为 5 << -4 是将二进制的 5 向右移动 4 次,对吗?这不意味着它应该像 5 >> 4 一样相等吗?我有点困惑如何进行位移.. :( - choz
@Choz应该这样吗?不,它没有被定义为这样。如果减号可以使移位方向反转,那会很好,但实际上并不是这样的。 - edc65
显示剩余2条评论

9
你的分析是正确的,除了你不应该将3(00011)的按位补码 ~3(11100)解释为-4,而是解释为一个无符号(即非负)的5位整数,即28 = 16 + 8 + 4(11100)。
这在ECMAScript标准中有解释(注意,在大多数现代机器中,正整数和负整数在内存中使用二进制补码表示):
12.8.3 左移运算符(<<)
注意:通过右操作数指定的数量对左操作数执行按位左移位运算。
12.8.3.1 运行时语义:评估
ShiftExpression : ShiftExpression << AdditiveExpression
  1. 将ShiftExpression求值得到lref。
  2. 将lref的值赋给lval。
  3. 如果lval有异常,则返回异常。
  4. 将AdditiveExpression求值得到rref。
  5. 将rref的值赋给rval。
  6. 如果rval有异常,则返回异常。
  7. 将lval转换为32位整数并赋值给lnum。
  8. 如果lnum有异常,则返回异常。
  9. 将rval转换为无符号32位整数并赋值给rnum。
  10. 如果rnum有异常,则返回异常。
  11. 将rnum的最低5位以外的所有位都清零,即计算rnum & 0x1F,并将结果赋值给shiftCount。
  12. 将lnum左移shiftCount位并返回结果。结果是一个带符号的32位整数。

7

~x 将反转 x 值的位表示(32 位有符号值,使用二进制补码表示)。

x << y 是左移运算符(在这里是向左)。您的数学解释是正确的 :)

您可以在此处阅读有关按位操作的更多信息:JavaScript 中的按位操作符


6

5 << ~3得到的结果与5 << -4相同,你是正确的。

重要的事情是:将x << y移位实际上会导致x * 2y,但这不是直接使用,它只是一个有用的副作用。
此外,如果y为负数,则不以相同的方式工作。


顺便问一下,如果有负数该怎么办?很抱歉我不太理解任何位表示。 - choz
1
,如果您有负的y值,它不会以相同的方式工作,因此实际上您没有回答实际问题。 - Jaromanda X

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接