In eight-bit two's complement, the sign bit can be interpreted as having the place value -28, which is of course -256. This is in fact precisely how the C standard characterizes it. Therefore, given an 8-bit value stored in a uint8_t
that you want to reinterpret as a two's complement integer, this is an arithmetic way to do so:
uint8_t u8 = /* ... */;
int8_t i8 = (u8 & 0x7f) - (u8 > 0x7f) * 0x100;
Note that all of the arithmetic is performed by first promoting the operands to (signed) int
, so there is neither overflow (because the range of int
is large enough for this) nor unsigned arithmetic wrap-around. The arithmetic result is guaranteed to be in the range of int8_t
, so there is no risk of overflow in the conversion of the result to that type, either.
You will note similarities between this computation and yours, but this one avoids the ternary operator by using the result of the relational expression u8 > 0x7f
(either 0 or 1) directly in the arithmetic, thus avoiding any branching, and it dispenses with needless casts. (Yours doesn't need the casts, either.)
Note also that if you run into some weird implementation that does not provide int8_t
(because its char
s are wider than 8 bits, or its signed char
s do not use two's complement) then that arithmetic approach still works in the sense of computing the right value, and you can be certain of safely recording that value in an int
orshort
. Thus, the absolutely most portable way to extract the value of 8-bit two's complement interpretation of a uint8_t would be
uint8_t u8 = /* ... */;
int i8 = (u8 & 0x7f) - (u8 > 0x7f) * 0x100;
Alternatively, if you are willing to rely on int8_t
to be a character type -- i.e. an alias for char
or signed char
-- then it is perfectly standard to do the job this way:
uint8_t u8 = /* ... */;
int8_t i8 = *(int8_t *)&u8;
That one is even more likely to be optimized away by a compiler than is the memcpy()
alternative presented in another answer, but unlike the memcpy
alternative, this one formally has undefined behavior if int8_t
turns out not to be a character type. On the other hand, both this and the memcpy()
approach depend on the implementation to provide type int8_t
, and even more unlikely than an implementation not providing int8_t
is that an implementation provides an int8_t
that fails to be a character type.
signed char
:i8 = (int8_t)*(signed char *)&u8;
, but that may not work on a hypothetical platform that does not use two's complement for its negative integer type representations. – Christian Gibbons