Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why Java unsigned bit shifting for a negative byte is so strange?

I have a byte variable:

byte varB = (byte) -1; // binary view: 1111 1111

I want to see the two left-most bits and do an unsigned right shift of 6 digits:

varB = (byte) (varB >>> 6);

But I'm getting -1 as if it was int type, and getting 3 only if I shift for 30!

How can I work around this and get the result only with a 6-digit shift?

like image 217
user2602807 Avatar asked Apr 16 '15 17:04

user2602807


2 Answers

The reason is the sign extension associated with the numeric promotion to int that occurs when bit-shifting. The value varB is promoted to int before shifting. The unsigned bit-shift to the right does occur, but its effects are dropped when casting back to byte, which only keeps the last 8 bits:

varB (byte)     : 11111111
promoted to int : 11111111 11111111 11111111 11111111
shift right 6   : 00000011 11111111 11111111 11111111
cast to byte    : 11111111

You can use the bitwise-and operator & to mask out the unwanted bits before shifting. Bit-anding with 0xFF keeps only the 8 least significant bits.

varB = (byte) ((varB & 0xFF) >>> 6);

Here's what happens now:

varB (byte)     : 11111111
promoted to int : 11111111 11111111 11111111 11111111
bit-and mask    : 00000000 00000000 00000000 11111111
shift right 6   : 00000000 00000000 00000000 00000011
cast to byte    : 00000011
like image 129
rgettman Avatar answered Oct 17 '22 21:10

rgettman


Because thats how shifting for bytes in java is defined in the language: https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.19.

The gist is that types smaller than int are silently widened to int, shifted and then narrowed back.

Which makes your single line effectively the equivalent of:

byte b = -1;      // 1111_1111
int temp = b;     // 1111_1111_1111_1111_1111_1111_1111_1111
temp >>>= 6;      // 0000_0011_1111_1111_1111_1111_1111_1111
b = (byte) temp;  // 1111_1111

To shift just the byte you need to make the widening conversion explicitly yourself with unsigned semantics (and the narrowing conversion needs to be manually, too):

byte b = -1;          // 1111_1111
int temp = b & 0xFF;  // 0000_0000_0000_0000_0000_0000_1111_1111
temp >>>= 6;          // 0000_0000_0000_0000_0000_0000_0000_0011
b = (byte) temp;      // 0000_0011
like image 42
Durandal Avatar answered Oct 17 '22 21:10

Durandal