26

我对负数的右移操作很困惑,这里是代码。

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

结果是:

11111111111111111111111111110001
11111111111111111111111111111111

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

4

3 回答 3

40

因为在 Java 中没有无符号数据类型,所以有两种类型的右移:算术移位 >>逻辑移位 >>>http://docs.oracle.com/javase/tutorial/java/nutsandbolts/op3.html

算术移位>>将保留符号位。
算术移位

无符号移位>>>不会保留符号位(因此填充0s)。
逻辑移位

(图片来自维基百科)


顺便说一下,算术左移和逻辑左移的结果是一样的,所以只有一个左移<<

于 2013-03-17T05:42:59.723 回答
21

运算符>>称为有符号右移,将所有位右移指定次数。重要的是>> 在移位后将最左边的符号位(Most Significant Bit 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

注意:最左边的三位是一位,因为在每个移位符号位都被保留并且每个位也是正确的。我写了符号被传播,因为所有这三位都是因为符号(而不是数据)。

也因为三个右移最右边的三位被损失掉了。

右两个箭头之间的位从-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是数量级的。

你有没有注意到 In this way -1 >> n相当于不是一个语句。
我相信如果有人这样做i = -1 >> n,它应该i = -1由 Java 编译器优化,但那是另一回事

接下来,有趣的是,在 Java 中还有一个右移运算符可用,>>>称为Unsigned Right Shift。它在逻辑上工作并为每个移位操作从左填充零。>>>因此,如果您对负数和正数都使用无符号右移运算符,那么在每次右移时,您总是会在最左边得到一个零位。

例子:

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(图形编程)中很有用,虽然我没有使用过它,但在过去读过它。

我建议您阅读以下内容:>>> 和 >> 之间的区别>>是算术右移,>>>是逻辑右移。

编辑

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

  • 无符号右移运算符>>>产生一个纯值,该值是左操作数右移零0扩展,其右操作数指定的位数。

  • >>and一样<<,运算符>>>也运算符从不抛出异常。

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

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

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

  • 如果左操作数的转换类型为 int,则仅将右操作数的值的最低 5 位用作移位距离。(即 2 5 = 32 位 = int 中的位数) 因此,移位距离在 0 到 31 的范围内。

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

    s==0 ? r : (r >> s) & ~(-1<<(32-s))
    
  • 如果左操作数的类型是长的,那么只有右操作数的值的六个最低有效位被用作移位距离。(即 2 5 = 64 位 = long 中的位数

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

    s==0 ? r : (r >> s) & ~(-1<<(64-s))
    

一个有趣的参考: [第 4 章] 4.7 移位运算符

于 2013-03-17T05:42:01.940 回答
3

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

于 2013-03-17T05:42:35.053 回答