'Merge uint16_t and int16_t into int32_t for data transmission in C

I am confronted with the problem, to transmit an index (uint16_t) and data (int16_t) in a int32_t type, but I had no success yet.

What I tried is the following:

int32_t buf = (int16_t) data;
buf |= ((int32_t idx)<<16);

But this works only for positive data because of the 2 complement, which adds a one at the MSB. How can I achieve this?



Solution 1:[1]

Merging integer types is a notorious problem in C due to finicky details about integer promotion, representations of integers, and the limits of the definitions of bit shifting.

First, there is rarely a good reason to merge a uint16_t and an int16_t into an int32_t for transmission. One ought to simply access the bytes of the objects using an unsigned char * and transmit the bytes. Often, gathering the bytes into a buffer for transmission is easily done using memcpy, which is equivalent to copying the bytes using an unsigned char *.

If you must merge a uint16_t and an int16_t into an int32_t, with the former going into the high bytes, then a standard-conforming way to do it is:

uint32_t temporary = (uint32_t) idx << 16 | (uint16_t) data;
int32_t buf;
memcpy(&buf, &temporary, sizeof buf);

Explanation:

  • (uint32_t) idx widens idx to 32 bits, ensuring that it can be shifted by 16 bits without losing any data.
  • (uint16_t) data converts data from int16_t, producing a non-negative value of 16 bits that will, when widened to 32 bits, remain non-negative (and not be subject to sign extension).
  • Then these two pieces are assembled into a uint32_t. (In general, it is easier to work with bit operations using unsigned integers, as they have better semantics for bit operations in C than signed integers do.)
  • Finally, the resulting bits are copied into buf, side-stepping problems with signed integer overflow and conversion.

Note that a good compiler will optimize away the memcpy.

To do this without memcpy, it is essentially necessary to adjust the uint16_t to a signed value:

int32_t temporary = 0x8000 <= idx ? idx - 0x10000 : idx;
int32_t buf = temporary * 0x10000 | (uint16_t) data;

Or, if the order of data and idx in buf can be changed, an easier solution is:

int32_t buf = data * 65536 | idx;

Solution 2:[2]

Try:

uint32_t buf = data & 0xffff; 
buf |= idx<<16;

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 stark