javaprocessingbitwise-operatorsbit-shiftsigned-integer

24- to 32-bit conversion in Java


I am interested in a new IoT project called OpenBCI, which is basically an open source EEG for reading and processing brain waves and other biodata. In their docs they claim that the data transmitted over-the-air (via RFDuino) sends 24-bit data. To convert the 24-bit values into 32-bit signed integers, they suggest the following Java-friendly Processing code:

int interpret24bitAsInt32(byte[] byteArray) {
    int newInt = (
        ((0xFF & byteArray[0]) << 16) |
        ((0xFF & byteArray[1]) << 8) |
        (0xFF & byteArray[2])
    );

    if ((newInt & 0x00800000) > 0) {
        newInt |= 0xFF000000;
    } else {
        newInt &= 0x00FFFFFF;
    }

    return newInt;
}

I guess I'm trying to understand exactly what is going on here. Let's take the first blurb of code:

int newInt = (
    ((0xFF & byteArray[0]) << 16) |
    ((0xFF & byteArray[1]) << 8) |
    (0xFF & byteArray[2])
);

The second segment is also a bit of a mystery:

if ((newInt & 0x00800000) > 0) {
    newInt |= 0xFF000000;
} else {
    newInt &= 0x00FFFFFF;
}

I guess there's just a lot of handwaving and magic going on with this function that I'd like to understand better!


Solution

  • Why is it safe to assume there are 3 bytes in the input?

    24 bits / 8 bits = 3 bytes

    What is the significance of the 0xFF value?

    It's a bit mask. 0b11111111 in binary. In this situation it's used as a quick way to convert the byte value into an int.

    What is the purpose of the left-bitshifting (<<)?

    Once the int value has been retrieved in the previous instruction it's shifted 16 bits to the left to take the position of the third byte of the int (counting from the right). This is due to the byte array storing the bytes in big-endian format, where the MSB comes first. The next byte is only shifted 8 bits to take up the position of the second byte, and the last one does not need to be shifted at all since it's already in the the first byte spot.

    Why newInt & 0x00800000? What's the significance of 0x00800000?

    Well 0x80 is another bit mask to get the MSB of a certain byte. 0x00800000 corresponds to the MSB of the third byte (i.e. the byte in byteArray[0] that was shifted 16 bits left in the previous process). This also corresponds to the MSB of the whole 24-bit value.

    Why the if-else based on postive vs. non-negative result of the above operation?

    Determining whether the MSB of the 24-bit value is 1 or 0 will tell us if it's negative or positive number, respectively, in two's complement notation.

    What is the significance of 0xFF000000 and 0x00FFFFFF?

    If the number is negative in it's 24 bit representation, we also want it to be negative as a 32-bit value in two's complement, that's why we have to fill up the 4th and final byte with 0b11111111 (0xFF) using the OR operator.
    If it was already positive then the fourth byte can be 0x00, with the following 3 bytes being the same as the original 24 bit value - accomplished by masking with 0x00FFFFFF.