Reputation: 11
I'm trying to split a byte (8 bits) into two 4 bit signed numbers. The resulting two 4 bit numers can be stored in an 8 bit data type (i.e, int8_t or signed char), but I need the sign to be preserved.
I seemed to have gotten the code below to do what I want, but I'm not sure why the bit shift of 12 works. Wouldn't that replace all bits with zeros when shifting left? I was thinking to shift the 4 high bits to the left to get 11110000, then arithmetic shift 4 bits right again to get 11111111. This is the two's complement representation of -1. But for some reason, shifting using 4 bits did not work, but rather shifting with 12 bits works. Any ideas?
int8_t b = 0b11101111;
int8_t dx = (b >> 4); // high bits
int8_t dy = (b << 12) >> 12; // low bits
In the code above, dx is -2 and dy is -1.
FYI, I'm using an Arduino Leonardo R3 and Arduino IDE 2.1.0.
Upvotes: 0
Views: 228
Reputation: 118435
The shown code makes use of three fundamental C++ concepts, and general computer science algorithms:
Implicit integer promotion rules, and the fact that your C++ compiler's int
s values are two byte integers, or 16 bits.
Two's complement arithmetic, which is how negative values are traditionally represented. TLDR: the high order bit indicates the sign of the value (together with a few other details).
Sign extension. When a smaller signed integer value gets promoted to a larger-sized integer value, sign extension takes place by extending the high-order bit to fill the higher-order bytes. This works together with two's complement in order to make integer promotion rules actually do what's advertised. Sign extension is also used with right shifts, which is basically the opposite operation, too, in order to, well, still wind up with a signed negative number after a right-shift. See a good computer science and algorithms textbook for a more complete explanation, discussion and concrete examples of sign extension, two's complement, and integer promotion rules.
int8_t dx = (b >> 4); // high bits
This uses implicit integer promotion (from a one-byte signed int
to a two-byte one) and sign extension, to extend the high-order bit in the 8-bit value to fill all 8 bits of the high order byte. Then a right-shift, according to the rules of 2s complement arithmetic extends the high-order bit as part of the right shift.
This is mostly a moot point, since the final conversion back to an 8-bit int
value ends up throwing all this hard work into trash, thanks to the well-established principle of "a C++ compiler can optimize away anything that has no observable effects", but it's important to understand the actual process in order to fully digest your main question:
int8_t dy = (b << 12) >> 12; // low bits
This again uses an implicit integer promotion rule to promote the initial b
to a two-byte int. The left shift ends up moving the 4th bit (using human-friendly base-1 numbering) into the 16th bit, the high order bit; then the right shift merrily extends the high order bit into the twelve high-order bits, in order to achieve the TLDR: extension of the 4th bit into the high four bits, of an 8-bit value, according to two's complement rules.
Upvotes: 2