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 int
.
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
byte aByte = -112; //0b1001_0000 | |
byte bByte = (byte) (aByte >> 4); //would expect 0b1111_1001 (-7) | |
System.out.println(bByte); //-7 | |
byte cByte = (byte) (aByte >>> 4); //would expect 0b0000_1001 (9) | |
System.out.println(cByte); //-7 |
For 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.
However for 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 1
s. 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 0b0000_1001
?
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.
byte dByte = (byte) ((aByte & 0xFF) >>> 4); //we get 0b0000_1001 (9) | |
System.out.println(dByte); //9 |
This is a bit clunky but a valuable technique if you are encountering unexpected results from your bitshifts!