Java: Bitshifting bytes
TL;DR When bitshifting a
byte use a
& 0xFF mask
Working at the bit level in Java can be very frustrating. This is for two reasons
- There are no unsigned number primitives
- Automatic type promotion is not intuitive!
Looking at type promotion, whenever you use a bitwise operator on a
byte variable it is automatically promoted to an
This means you have to write casting code such as
byte b = (byte) (b ^ 8); or
b ^= 8 which does the former under-the-hood.
This is not too bad really. But the pain starts when working with signed numbers together with
int promotion. For example
bByte we would expect the result to be
0b1111_1001 as the right Arithmetic shift operator
>> fills the left bit depending on the left most (sign) bit (which is
1 when negative ala 2’s complement) so the result is as expected.
cByte we would expect the result to be
0b0000_1001 as the right Logical shift operator
>>> should fill the left most bit with
0, but we still get
-7! Why is this happening?
Well due to the auto int promotion (as a result of using any bitwise operator), when the
aByte is promoted to an
int, the left most bits of that int are all
1s. In binary form this is
0b1111_1111_1111_1111_1111_1111_1001_0000 before the shift
0b0000_1111_1111_1111_1111_1111_1111_1001 after the shift
Due to how casting works when casting down the last 8-bits will be copied directly, hence still giving
0b1111_1001. Not very intuitive when you see
aByte >>> 4 imho.
So how can we perform
>>> operations and get the “intuitive” result of
Note: This is useful as to be able to do as Java does not have an unsigned byte type the above kind of operation would let you use a java
byte in place of another languages
unsigned byte type. This can useful when porting code, for example, if you want to replicate a
ubyte >> 4 call then the below will be useful.
The answer is to mask the bitwise result before casting back down to
byte. We can do this with
& 0xFF. This works by persevering only the last 8 bits of the promotioned-to
int only, and dropping all the extra
1 bits.When casting down we then get the result we are looking for. I.e.
This is a bit clunky but a valuable technique if you are encountering unexpected results from your bitshifts!