Search code examples
javabyte-shifting

Why left shifting in java changing the sign value


I am working on java. I am wondering why java producing this output. I am sharing the code here.

public class vvn {

    public static void main(String[] args)
    {
        byte [] arr = new byte[4];
        arr[0] = (byte)157;
        arr[1] = 1;
        arr[2] = 0;
        arr[3] = 0;
        System.out.format("read 0x%x 0x%x 0x%x 0x%x \n",arr[3],arr[2],arr[1],arr[0]);
        int v = (arr[0] | (arr[1] << 8) | (arr[2] << 16) | (arr[3] << 24));
        System.out.format("read 0x%x\n",v);

    }

}

And I got the output as

read 0x0 0x0 0x1 0x9d 
read 0xffffff9d

I expected the output should be 0x0000019d


Solution

  • You are converting from byte (signed 8 bits) to integer (signed 32 bits). The most significant bit (the leftmost one) holds the sign (see two's complement).

    Your 157 is 10011101 in binary. Since you assign this value to a signed byte (java has no unsigned byte), this is in fact a negative number, -99.

    Now when you convert from byte to int, the value is preserved. In case of negative numbers, this means setting all the bits to the left to preserve the signedness. In your case, 10011101 becomes 11111111 11111111 11111111 10011101.

    Anyway, working with unsigned bytes in java is a nightmare. Basically, you need to mask everything with 0xff (to cut off the 'ones to the left') like this:

    int v = ((arr[0] & 0xff) |
        ((arr[1] & 0xff) << 8) |
        ((arr[2] & 0xff) << 16) |
        ((arr[3] & 0xff) << 24));
    

    Beautiful, isn't it?

    Update 1: Also, you may be interested in Guava's UnsignedBytes...

    Update 2: Java 8 Byte has toUnsignedInt() and toUnsignedLong() methods. Your calculation thus becomes:

    int v = (Byte.toUnsignedInt(arr[0]) |
        (Byte.toUnsignedInt(arr[1]) << 8) |
        (Byte.toUnsignedInt(arr[2]) << 16) |
        (Byte.toUnsignedInt(arr[3]) << 24));