Java位移操作的奇怪行为

5

Java有两个位移运算符用于右移操作:

>> shifts right, and is dependant on the sign bit for the sign of the result

>>> shifts right and shifts a zero into leftmost bits

http://java.sun.com/docs/books/tutorial/java/nutsandbolts/op3.html

这个问题看起来很简单,但是请问有人能解释为什么当bar的值为-128时,下面的代码会使foo的值变成-2:

byte foo = (byte)((bar & ((byte)-64)) >>> 6);

这个功能的目的是取一个8位字节,屏蔽掉左侧的2位,然后将它们移动到右侧的2位。例如:

initial = 0b10000000 (-128)
-64 = 0b11000000
initial & -64 = 0b10000000
0b10000000 >>> 6 = 0b00000010

实际上,结果是-2。
0b11111110

将1秒左移而不是零位

3个回答

8

这是因为 & 实际上会将值提升为 int 类型 - 这样就留下了很多 "1" 位。然后您进行右移,将最左边的 2 位设置为 0,但是然后通过转换回字节来忽略那些最左边的位。

如果您将操作分开,这就变得更清晰了:

public class Test
{
    public static void main(String[] args)
    {
        byte bar = -128;
        int tmp = (bar & ((byte)-64)) >>> 6;
        byte foo = (byte)tmp;
        System.out.println(tmp);
        System.out.println(foo);
    }
}

打印

67108862
-2

现在让我们再来进行一次位运算:

initial = 0b10000000 (-128)
-64 = 0b11000000
initial & -64 = 0b11111111111111111111111110000000 // it's an int now
0b10000000 >>> 6 = 0b00111111111111111111111111100000 // note zero-padding
(byte) (0b10000000 >>> 6) = 11100000 // -2

即使你通过强制转换得到了正确的&运算结果,在使用>>>时,它仍会先将第一个操作数提升为int类型。
编辑:解决方法是改变掩码方式。不要使用-64进行掩码,而是使用128+64=192=0xc0进行掩码。
byte foo = (byte)((bar & 0xc0) >>> 6);

这样你就只剩下了想要的两个比特,而不是在最高有效位的24个比特中有一堆1。


@Martin:不会的,因为位移运算也会实现类型提升。我会在一分钟内编辑一个解决方案。 - Jon Skeet
非常感谢,显然你的bitfoo比我的更大 ;) - Martin
请注意,这里不需要使用 >>>,因为符号位已经被清除。此外,原帖作者想要的是前两位,即 0xC0。 - PSpeed

2

其他人已经告诉你为什么,但我会更进一步地解释,并提供真正的解决方案。

byte foo = (byte)((bar & ((byte)-64)) >>> 6);

由于 & 运算符会将所有内容提升为 int 类型,因此这实际上是做了以下操作:

byte foo = (byte)(((int)bar & (int)((byte)-64)) >>> 6);

如果bar为-128,那么(int)bar就是0xFFFFFF80,然后再与0xFFFFFFC0进行&操作...这个结果是:0xFFFFFF80,你需要将其向右移动6位以得到:0x3FFFFFFE。
正确答案非常简单:
byte foo = (byte)((bar & 0xC0) >> 6);

将bar提升为int类型以进行“&”操作,因此在该int中仅剩下原始字节的前两位。然后将其右移6位并转换回字节。


你说0xC0,Jon说0xD0。我想我必须检查一下你们两个的逻辑... - Martin
@Martin:0xC0 是正确的。我已经修正了我的回答。真是尴尬 :) - Jon Skeet
我开始得出这个结论,我正在进行三重检查它,从不怀疑伟大的 Skeet! ;) - Martin

2
据我所知,在Java中,大多数运算符(+、-、>>、&等)不能用于比int更小的数据类型。因此,你的位移操作和&会在后台将值隐式转换为int,然后通过显式转换将其转换回byte。最后一次转换会丢弃高位中的零。
如果要得到预期的结果,请在int上进行这些操作。

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