Weird behaviour of bit-shifting with byte in Java

2019-02-24 05:38发布

问题:

As I was using bit-shifting on byte, I notice I was getting weird results when using unsigned right shift (>>>). With int, both right shift (signed:>> and unsigned:>>>) behave as expected:

    int min1 = Integer.MIN_VALUE>>31; //min1 = -1
    int min2 = Integer.MIN_VALUE>>>31; //min2 = 1

But when I do the same with byte, strange things happen with unsigned right shift:

    byte b1 = Byte.MIN_VALUE; //b1 = -128
    b1 >>= 7; //b1 = -1

    byte b2 = Byte.MIN_VALUE; //b2 = -128
    b2 >>>= 7; //b2 = -1; NOT 1!
    b2 >>>= 8; //b2 = -1; NOT 0!

I figured that it could be that the compiler is converting the byte to int internally, but does not seem quite sufficient to explain that behaviour.

Why is bit-shifting behaving that way with byte in Java?

回答1:

This happens exactly because byte is promoted to int prior performing bitwise operations. int -128 is presented as:

11111111 11111111 11111111 10000000

Thus, shifting right to 7 or 8 bits still leaves 7-th bit 1, so result is narrowed to negative byte value.

Compare:

System.out.println((byte) (b >>> 7));           // -1
System.out.println((byte) ((b & 0xFF) >>> 7));  //  1

By b & 0xFF, all highest bits are cleared prior shift, so result is produced as expected.



回答2:

Shift operators for byte, short and char are always done on int.

Therefore, the value really being shifted is the int value -128, which looks like this

int b = 0b11111111_11111111_11111111_10000000;

When you do b2 >>= 7; what you are really doing is shifting the above value 7 places to the right, then casting back to a byte by only considering the last 8 bits.

After shifting 7 places to the right we get

        0b11111111_11111111_11111111_11111111;

When we convert this back to a byte, we get just 11111111, which is -1, because the byte type is signed.

If you want to get the answer 1 you could shift 31 places without sign extension.

byte b2 = Byte.MIN_VALUE; //b2 = -128
b2 >>>= 31;
System.out.println(b2);   // 1


回答3:

Refer to JLS 15.19 Shift Operators:

Unary numeric promotion (§5.6.1) is performed on each operand separately.

and in 5.6.1 Unary Numeric Promotion :

if the operand is of compile-time type byte, short, or char, it is promoted to a value of type int by a widening primitive conversion

So, your byte operands are promoted to int before shifting. The value -128 is 11111111111111111111111110000000 .

After the shifting 7 or 8 times, the lowest 8 bits are all 1s, which when assigning to a byte, a narrowing primitive conversion occurs. Refer to JLS 5.1.3 Narrowing Primitive Conversion :

A narrowing conversion of a signed integer to an integral type T simply discards all but the n lowest order bits, where n is the number of bits used to represent type T.