what is the meaning in C++ when casting both (int) and (int16_t)

I am studying a C++ code in Arduino example for reading external data. The code use casting (int16_t) and also (int).

int16_t is fixed integer type, but I don’t understand it’s purpose in the code.

_vRaw[0] = (int)(int16_t)(Wire.read() | Wire.read() << 8);

Is there any difference if I write like below?

_vRaw[0] = (int)(Wire.read() | Wire.read() << 8);

Answer

I’ve written code like this before so I can explain the purpose of the cast to int16_t.

The Arduino command Wire.read() generally returns a number between 0 and 255 representing a byte read from the I2C device.

Sometimes, two of the bytes you read from a device will represent a single signed 16-bit number using Two’s complement. So, for example, if the first byte is 1 and the second byte is 2, we want to interpret that as a value of 1 + 2*256 = 513. If both bytes are 255, we want to interpret that as -1.

Let’s assume that your code is being compiled for a system where int is 32-bit (almost any 32-bit microcontroller), and both bytes are 255, and think about this expression:

(Wire.read() | Wire.read() << 8)

The value of this expression would simply be 255 | (255 << 8) which is 65535. That is bad; we wanted the value to be -1.

The easy way to fix that bug is to add a cast to int16_t:

(int16_t)(Wire.read() | Wire.read() << 8)

When we cast 65535 to an int16_t type, we get -1 because an int16_t cannot hold 65535 (it can only hold values from -32768 to 32767). (The C++ standard does not guarantee that we get -1, but it is implementation defined behavior so you can check the manual of your compiler if you want to make sure.) If we later cast it back to an int (either with an explicit cast or by setting some int equal to it), the compiler will do the correct signed extension operation and give us an int with a value of -1.

By the way, I’m not confident that your compiler will run the two calls to Wire.read() in the right order unless you have a semicolon between then. Also, the final cast to int is probably not needed. So I would rewrite the code to be something like this:

uint8_t lsb = Wire.read();
_vRaw[0] = (int16_t)(lsb | Wire.read() << 8);