简体   繁体   中英

Compound operator with shift in C or how to convert 20bit 2'complement number in 32bit signed int

There is 20 bit 2'complement number (which is read with 3 x 8 bit) and it needs to be converted in 32bit signed int.

Could someone please explain this piece of code:

int32_t sample = 0;
sample |= SPI.transfer(0);
sample <<= 8;
sample |= SPI.transfer(0);
sample <<= 8;
sample |= SPI.transfer(0);
sample <<= 8;
sample /= 1L << 12;

So now in 32bit signed integer from right the right side there is 24 values and then:

sample /= 1L << 12;

How this works?

Link for the full code is at:

https://github.com/circuitar/Nanoshield_LoadCell/blob/master/src/Nanoshield_LoadCell.cpp

In general, bit-wise shift operations should be done on unsigned integer types because:

  • Left-shifting a negative value results in undefined behavior.
  • Left-shifting a non-negative, signed value results in undefined behavior if any non-zero bits are shifted to or through the position of the sign bit.
  • Right-shifting a negative value produces an implementation-defined value.

If you are careful to avoid all of the above, bit-wise shift operations can be used portably on signed integer types as in the following 1 :

int32_t sample = 0;
sample |= SPI.transfer(0);
sample <<= 8;
sample |= SPI.transfer(0);
sample <<= 8;
sample |= SPI.transfer(0);
sample >>= 4;
// 'sample' contains a 20-bit, 2's complement value. Sign-extend to a 32-bit signed value.
if (sample >= (INT32_C(1) << 19))
    sample -= (INT32_C(1) << 20);

The if (sample >=... sign-extension part might not be branch-free, depending on the compiler. An alternative sign-extension conversion from 20-bit 2's complement to 32-bit signed is:

sample = (sample ^ (INT32_C(1) << 19)) - (INT32_C(1) << 20);

The XOR operation converts the 2's complement value to an offset binary value. That operation could be merged into the value of the byte from the first SPI transfer (the most significant 8 bits of the 20-bit sample value) as follows:

int32_t sample = 0;
sample |= SPI.transfer(0) ^ 0x80; // convert 2's complement to offset binary
sample <<= 8;
sample |= SPI.transfer(0);
sample <<= 8;
sample |= SPI.transfer(0);
sample >>= 4;
// 'sample' contains a 20-bit, offset binary value. Convert to 32-bit, signed value.
sample -= INT32_C(1) << 20;

1 "Portably" here as long as int32_t is provided by the implementation. If not, then int_least32_t can be used instead.

How this works?

If SPI.transfer(0) returns 0-255, it "works" when the 20-bit number is in the upper 24 bits of data read. Then shifting that into the 32-bit type sign-bit relies on UB to form the correct value that when divided but 1 << 12 is the sought after value.

To convert a 20-bit 2's complement number to a int32_t , test the sign bit.

// Alternative without UB nor implementation defined behavior: 

int32_t sample = 0;
sample |= SPI.transfer(0);
sample <<= 8;
sample |= SPI.transfer(0);
sample <<= 8;
sample |= SPI.transfer(0);

// if input is in upper 20 bits of the 24 read (OP is not explicit on that)
if (1) {
  sample >>= 4;
}

assert(sample >= 0 && sample <= 0xFFFFF);
if (sample & 0x80000) { // Test for the 20-bit 2's complement sign bit
  sample -= 0x100000;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM