我对负数的右移操作非常困惑,以下是代码。
int n = -15;
System.out.println(Integer.toBinaryString(n));
int mask = n >> 31;
System.out.println(Integer.toBinaryString(mask));
结果是:
11111111111111111111111111110001
11111111111111111111111111111111
为什么将一个负数右移31位而不是1位(符号位)?
我对负数的右移操作非常困惑,以下是代码。
int n = -15;
System.out.println(Integer.toBinaryString(n));
int mask = n >> 31;
System.out.println(Integer.toBinaryString(mask));
结果是:
11111111111111111111111111110001
11111111111111111111111111111111
为什么将一个负数右移31位而不是1位(符号位)?
>>
和逻辑右移logical shift>>>
。http://docs.oracle.com/javase/tutorial/java/nutsandbolts/op3.html
算术右移>>
将保留符号位。>>>
不会保留符号位(因此填充0
)。<<
。10101010 << 1
**,会得到什么结果?是 11010100
还是 **01010100
**? - Mmmh mmh>>
操作符称为 有符号右移,将所有位向右移动指定次数。重要的是 >>
将最左端的符号位(最高有效位 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,因为在每次移位时符号位被保留,并且每个位都向右移动。我写下了“符号被传递”,因为这三个位都是由符号(而不是数据)导致的。 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))
一个有趣的参考资料:[第4章] 4.7 移位运算符
因为 >> 被定义为算术右移,它保留符号。要获得您期望的效果,请使用逻辑右移运算符 >>>。
>>> -1
并且它适用于int
和long
类型。 - Peter Lawrey