The mystery of the negative byte value in Java

A story of bits, bytes, signed and unsigned

Some time ago there was a question on the Pi4J-forum caused by some confusion about a numeric value handled as a byte which was logged as a negative number -86 instead of the expected value 170. So this is my attempt to try to solve this mystery… ;-)

The basics

A lot of interaction with the GPIO’s requires some understanding of bits/bytes. So let’s dive into a quick summary.

Very short:

If you go further in the chain you get this:

Convert bits to a numeric and hex value

A combination of multiple bits is converted to a number by using the power of 2.

In everyday life we are used to decimal values where we group everything by 10, 20, 30. In programming, hexadecimal values are used more, and they have the range 0 to 15, which perfectly matches the maximum value of four bits (“1111”). A hex value is written as x0 to xF.

The following table shows all possible combinations of 4 bits ranging from “0000” to “1111”:

All combinations with four bits

Calculate a byte value

A byte consists of 8 bits and has the range of 0x00 (= 0) to 0xFF (= 255).

So we need to extend the table above to have 8 bits. Let’s take a few examples:

Calculate a byte value

Values in Java

Let’s check in Java how values are represented with the following code:

class PrintLimits {
    public static void main(String[] args) {
        System.out.println("Byte");
        System.out.println("    Min: " + Byte.MIN_VALUE);
        System.out.println("    Max: " + Byte.MAX_VALUE);

        System.out.println("Short");
        System.out.println("    Min: " + Short.MIN_VALUE);
        System.out.println("    Max: " + Short.MAX_VALUE);

        System.out.println("Integer");
        System.out.println("    Min: " + Integer.MIN_VALUE);
        System.out.println("    Max: " + Integer.MAX_VALUE);

        System.out.println("Long");
        System.out.println("    Min: " + Long.MIN_VALUE);
        System.out.println("    Max: " + Long.MAX_VALUE);
    }
}

As a result we get these values:

Byte
    Min: -128
    Max: 127
Short
    Min: -32768
    Max: 32767
Integer
    Min: -2147483648
    Max: 2147483647
Long
    Min: -9223372036854775808
    Max: 9223372036854775807

Hmm, this is unexpected! A byte has the range of -128 to 127, instead of 0 to 255?!
To understand this, we need to know the difference between signed and unsigned values.

Signed versus unsigned

When you calculate the byte value to a signed number value, the major bit (the most left one) is handled as an indicator for a negative number (1) or positive number (0), for example 8 bits “1000 1111”:

Conversion of 8 bits

“1000 1111” is both -15 and 143 depending if you handle it as signed or unsigned.

The same goes for a short which consist of two bytes (= 16 bits), for example “1000 0000 0000 1111”:

Conversion of 16 bits

“1000 0000 0000 1111” is both -15 and 32.783 depending if you handle it as signed or unsigned.

Conclusion

You need to be sure how you handle numeric values when you read them out to not confuse between the signed and unsigned value!

Luckily we have the Byte.toUnsignedInt(b) function, to make sure we are reading out the correct value when logging the values. Here a quick demo with the 170 value from the original Pi4J-forum question:

class HexIntegerToString {
    public static void main(String[] args) {
        convertByte((byte) 170);
    }

    private static void convertByte(byte value) {        
        System.out.println("Byte Unsigned: " + Byte.toUnsignedInt(value) + "\tSigned: " + value);
        System.out.println("    Hex value:  0x" + Integer.toHexString(value & 0xFF));
        System.out.println("    Binair:     " + Integer.toBinaryString(value & 0xFF));
    }
}

Which gives this output:

Byte Unsigned: 170      Signed: -86
    Hex value:  0xaa
    Binair:     10101010