我对负数的右移操作非常困惑,这是代码。
int n = -15;
System.out.println(Integer.toBinaryString(n));
int mask = n >> 31;
System.out.println(Integer.toBinaryString(mask));
结果是:
11111111111111111111111111110001
11111111111111111111111111111111
为什么要将负数右移31而不是1(符号位)?
答案 0 :(得分:35)
因为在Java中没有无符号数据类型,所以有两种类型的右移:arithmetic shift >>
和logical shift >>>
。 http://docs.oracle.com/javase/tutorial/java/nutsandbolts/op3.html
算术移位>>
将保持符号位
无符号移位>>>
不会保留符号位(因此填充0
s。)
(来自维基百科的图片)
顺便说一下,算术左移和逻辑左移都有相同的结果,因此只剩下一个左移<<
。
答案 1 :(得分:14)
操作员>>
调用签名右移,将所有位向右移动指定的次数。重要的是>>
将最左边的符号位(最高有效位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,则仅使用右操作数的五个最低有效位作为移位距离。 (即2 5 = 32位= int中的位数)
因此,移位距离在0到31的范围内。
此处,r >>> s
生成的值与:
s==0 ? r : (r >> s) & ~(-1<<(32-s))
如果左操作数的类型很长,则只使用右操作数值的六个最低有效位作为移位距离。(即2 5 = 64位=长位数)
此处,r >>> s
生成的值与以下内容相同:
s==0 ? r : (r >> s) & ~(-1<<(64-s))
答案 2 :(得分:3)
因为&gt;&gt;被定义为算术右移,它保留了符号。为了获得您期望的效果,请使用逻辑右移,&gt;&gt;&gt;操作