Why left shifting in java changing the sign value

921 views Asked by At

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

5

There are 5 answers

0
prook On BEST ANSWER

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));
3
Rupesh On

Because the left most digit is reserved for sign, when you shift left the sign changes when you commit an overflow.

0
Solorad On

Look. Your expression (arr[1] << 8) | (arr[2] << 16) | (arr[3] << 24) equals to Ox100. So, you need extra Ox9d, which is in decimal is 157. Unfortunatly, byte diapasone is [-128; 127]. So, it is impossible to get 157 from byte. And your a[0] is equal to -99.

0
Hoopje On

In Java, The type byte is a signed value from -128 to 127. This means that (byte)157 result in an overflow and is actually equal to -99. When you output the hexadecimal value of the byte is not apparerent, because the binary representation of -99 (signed) and 157 (unsigned) is equal (and the format conversion %x interprets the value as an unsigned value).

However, as soon as you use it in an expression, the byte value is promoted to an int. And the hexadecimal representation of -99 as an int is 0xffffff9d. You can see this by adding the following line to your program:

System.out.format("0x%x \n", (int)arr[0]);
0
radoh On

I believe the problem is that bitwise operations in java are performed on ints So when you use

arr[0] | ...

arr[0] is converted to int. So instead of the expected 0x9d, you get 0xffffff9d So the solve the issue you need to & the 0xff mask, e.g.

int v = ((arr[0] & 0xff) | ((arr[1] & 0xff) << 8) | ...;