Java:在负数上右移
我对负号右移操作很困惑,这里是代码。
int n = -15; System.out.println(Integer.toBinaryString(n)); int mask = n >> 31; System.out.println(Integer.toBinaryString(mask));
结果是:
11111111111111111111111111110001 11111111111111111111111111111111
为什么右移一个负数不是1(符号位)?
因为在Java中没有无符号的数据types,所以有两种types的右移: 算术移位 >>
和逻辑移位 >>>
。 http://docs.oracle.com/javase/tutorial/java/nutsandbolts/op3.html
算术移位>>
将保持符号位。
无符号移位>>>
将不会保留符号位(因此填充0
s)。
(来自维基百科的图片)
顺便说一下,算术左移和逻辑左移都有相同的结果,所以只有一个左移<<
。
操作员>>
称为右移 ,将所有位右移指定的次数。 重要的是>>
填充最左边的符号位(最高有效位MSB)到最左边的位后移。 这就是所谓的符号扩展 ,当你把它们移到正确的位置时,它可以保留负数的符号 。
下面是我的示意图,用一个例子来说明这是如何工作的(对于一个字节):
例:
i = -5 >> 3; shift bits right three time
5的补码forms是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
。
你是否注意到这样-1 >> n
等于不是一个声明。
我相信如果一个人做i = -1 >> n
它应该优化到i = -1
的Java编译器,但这是不同的问题
接下来,在Java中知道更多的右移运算符是有用的>>>
称为无符号右移 。 它在逻辑上工作并且从每次换class操作的左侧填充零。 因此,在每次右移时,如果对正负数都使用无符号右移>>>
运算符,则总是会在最左边的位置得到一个零位。
例:
i = -5 >>> 3; Unsigned shift bits right three time
下面是我的图演示如何expression式-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(graphics编程)中是有用的,尽pipe我没有使用它,但是在过去的某些地方读过它。
我build议你阅读这个: >>>和>>之间的区别 : >>
是算术右移, >>>
是逻辑右移。
编辑 :
一些有趣的关于无符号右移运算符>>>
运算符。
-
无符号的右移运算符
>>>
产生一个纯粹的值,它的左操作数右移00
扩展右操作数指定的位数。 -
像
>>
和<<
,运算符>>>
运算符也不会抛出exception。 -
无符号右移运算符的每个操作数的types必须是整型数据types,否则会发生编译时错误。
-
>>>
操作符可以对其操作数执行types转换; 与算术二元运算符不同,每个操作数都是独立转换的。 如果操作数的types是byte,short或char,则在计算运算符的值之前,将该操作数转换为int。 -
无符号右移运算符生成的值的types是其左操作数的types。
LEFT_OPERAND >>> RHIGT_OPERAND
-
如果左操作数的转换types是int,则只有右操作数值的五个最低有效位用作移位距离。 ( 即2 5 = 32位= int中的位数 )
所以移动距离在0到31之间。这里,
r >>> s
产生的值与下面的一样:s==0 ? r : (r >> s) & ~(-1<<(32-s))
-
如果左操作数的types很长,那么只有右操作数值的6个最低有效位被用作移位距离( 即2 5 = 64位=长位数 )
这里,
r >>> s
产生的值与下面相同:s==0 ? r : (r >> s) & ~(-1<<(64-s))
一个有趣的参考: [第4章] 4.7移位运算符
因为>>被定义为一个算术右移,保留了符号。 为了达到预期的效果,请使用逻辑右移>>>操作符。