Java:负数的右移

27

我对负数的右移操作非常困惑,以下是代码。

int n = -15;
System.out.println(Integer.toBinaryString(n));
int mask = n >> 31;
System.out.println(Integer.toBinaryString(mask));

结果是:

11111111111111111111111111110001
11111111111111111111111111111111

为什么将一个负数右移31位而不是1位(符号位)?


2
顺便提一下,您可以使用 >>> -1 并且它适用于 intlong 类型。 - Peter Lawrey
3个回答

44

如果你执行 **10101010 << 1**,会得到什么结果?是 11010100 还是 **01010100**? - Mmmh mmh
6
我的意思是,“顺便说一下,算术位移和逻辑位移得到的结果相同,因此只有一个左移 << ”这句话确实正确吗? - Mmmh mmh

22

>> 操作符称为 有符号右移,将所有位向右移动指定次数。重要的是 >> 将最左端的符号位(最高有效位 MSB)填充到移位后最左边的位上。这被称为 符号扩展,用于在将负数右移时保留其符号。

下面是我的图示表示,并通过一个示例来展示它是如何工作的(对于一个字节):

示例:

i = -5 >> 3;  shift bits right three time 

二进制补码形式的五是1111 1011

内存表示:

 MSB
+----+----+----+---+---+---+---+---+
|  1 |  1 | 1  | 1 | 1 | 0 | 1 | 1 |   
+----+----+----+---+---+---+---+---+
   7    6   5    4   3   2   1   0  
  ^  This seventh, the left most bit is SIGN bit  

以下是>>的工作原理。当您执行-5 >> 3时,

                        this 3 bits are shifted 
                         out and loss
 MSB                   (___________)      
+----+----+----+---+---+---+---+---+
|  1 |  1 | 1  | 1 | 1 | 0 | 1 | 1 |   
+----+----+----+---+---+---+---+---+
  | \                 \  
  |  ------------|     ----------|
  |              |               |
  ▼              ▼               ▼
+----+----+----+---+---+---+---+---+
|  1 |  1 | 1  | 1 | 1 | 1 | 1 | 1 |
+----+----+----+---+---+---+---+---+
(______________)
 The sign is        
 propagated
注意:最左侧的三位是1,因为在每次移位时符号位被保留,并且每个位都向右移动。我写下了“符号被传递”,因为这三个位都是由符号(而不是数据)导致的。
此外,由于右移三位,最右侧的三位将丢失。
右箭头之间的位从“-5”中的先前位中暴露出来。
我认为如果我也写一个正数的例子会很好。下一个例子是“5 >> 3”,五是一个字节是“0000 0101”。
                        this 3 bits are shifted 
                         out and loss
 MSB                   (___________)      
+----+----+----+---+---+---+---+---+
|  0 |  0 | 0  | 0 | 0 | 1 | 0 | 1 |   
+----+----+----+---+---+---+---+---+
  | \                 \  
  |  ------------|     ----------|
  |              |               |
  ▼              ▼               ▼
+----+----+----+---+---+---+---+---+
|  0 |  0 | 0  | 0 | 0 | 0 | 0 | 0 |
+----+----+----+---+---+---+---+---+
(______________)
 The sign is        
 propagated

再看一遍我写的内容:符号位传递,所以最左边的三个零是由于符号位造成的。

这就是运算符>>带符号右移所做的事情,保留左操作数的符号。

[你的翻译]
在你的代码中,你使用>>运算符将-15向右移动31次,因此你最右边的31位被丢弃,结果是所有的位都是1,实际上是大小为-1
你是否注意到用这种方式对-1 >> n进行操作等价于无操作?
我认为,如果一个人执行i = -1 >> n,它应该被Java编译器优化为i = -1,但这是另一回事

接下来,有趣的是在Java中还提供了另一个右移运算符>>>,称为无符号右移。它按逻辑进行移位,并在每次移位操作时从左侧填充零。 因此,如果对负数和正数使用无符号右移>>>运算符,则每次右移都会在最左边的位置上得到一个零位。

示例:

i = -5 >>> 3;  Unsigned shift bits right three time 

下面是我的图示,演示了表达式 -5 >>> 3 的工作原理。

                        this 3 bits are shifted 
                         out and loss
 MSB                   (___________)      
+----+----+----+---+---+---+---+---+
|  1 |  1 | 1  | 1 | 1 | 0 | 1 | 1 |   
+----+----+----+---+---+---+---+---+
  | \                 \  
  |  ------------|     ----------|
  |              |               |
  ▼              ▼               ▼
+----+----+----+---+---+---+---+---+
|  0 |  0 | 0  | 1 | 1 | 1 | 1 | 1 |
+----+----+----+---+---+---+---+---+
(______________)
  These zeros
  are inserted  

注意到:这一次我没有写“符号位传播”,而是实际上将>>>操作符插入了零。因此,>>>不保留符号,而是进行逻辑右移。

据我所知,无符号右移在VDU(图形编程)中很有用,虽然我自己没有使用过,但在以前的某个地方读到过。

我建议您阅读这篇文章:Difference between >>> and >>>>是算术右移,>>>是逻辑右移。

编辑:

一些关于无符号右移运算符>>>运算符的有趣信息。

  • 无符号右移运算符>>>通过将其左操作数右移指定数量的位数并进行零0扩展来产生纯值。

  • >><<一样,操作符>>>也从不抛出异常。

  • 无符号右移运算符的每个操作数的类型必须是整数数据类型,否则会引发编译时错误。

  • >>>运算符可能对其操作数执行类型转换;与算术二进制运算符不同,每个操作数都是独立转换的。如果操作数的类型为byte、short或char,则在计算运算符的值之前将该操作数转换为int。

  • 无符号右移运算符产生的值的类型是其左操作数的类型。LEFT_OPERAND >>> RHIGT_OPERAND

  • 如果左操作数的转换类型是int,则仅使用右操作数最后五位作为移位距离。(即25 = 32位= int中的位数)因此,移位距离在0到31之间。

    在这里,r >>> s产生的值与以下值相同:

s==0 ? r : (r >> s) & ~(-1<<(32-s))
如果左操作数的类型是long,则仅使用右操作数值的六个最低有效位作为移位距离。(即2的5次方=64位=long类型的位数)在这里,由r >>> s产生的值与以下内容相同:
s==0 ? r : (r >> s) & ~(-1<<(64-s))

  • 1
    一个更简单的答案既更正确也更有用。运算符不会区分操作数是负数还是正数,然后执行两个不同的操作。 - user207421
    1
    你可以尝试简化你的回答,就像建议的那样。我不知道为什么这是个谜。我给了你一些措辞:“它只是保留符号位,无论它是什么”,还有其他正确的答案可以参考。然后你甚至可能会得到一些点赞。你描述的效果确实存在,但不是你描述的方式。你似乎很难理解这一点。 - user207421
    1
    @GrijeshChauhan - 我同意EJP的观点。这个答案自相矛盾,非常令人困惑。 - Stephen C
    1
    @GrijeshChauhan 我的意思是让你改进这个答案。这就是我最初评论和整个对话的原因。 - user207421
    1
    感谢您提供详细的描述,让我感到非常开心。 - Ajay
    显示剩余4条评论

    3

    因为 >> 被定义为算术右移,它保留符号。要获得您期望的效果,请使用逻辑右移运算符 >>>。


    好的,我想说的是你用一个词“保留符号”来表达,这很好。 - Grijesh Chauhan

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