C allows for three different representations of signed integers, but the most common is "two's complement". However, I'll briefly discuss "one's complement" as well to illustrate how there is a relationship between them.
One's complement
One's complement integers are split into sign and value bits. To use the 8-bit representation of the integer 19
as an example:
S|64 32 16 8 4 2 1
0| 0 0 1 0 0 1 1 = 19
Using the bitwise complement operator ~
flips all of the bits of the integer, including the sign bit:
S|64 32 16 8 4 2 1
1| 1 1 0 1 1 0 0 = ~19
When the sign bit is set, the interpretation of 1 and 0 bits is reversed (0=on, 1=off), and the value is considered negative. This means the value above is:
-(16 + 2 + 1) = -19
Two's complement
Unlike one's complement, an integer is not divided into a sign bit and value bits. Instead, what is regarded as a sign bit adds -2^(b - 1)
to the rest of the value, where b
is the number of bits. To use the example of an 8-bit representation of ~19
again:
-128 64 32 16 8 4 2 1
1 1 1 0 1 1 0 0 = ~19
-128 + 64 + 32 + 8 + 4
= -128 + 108
= -(128 - 108)
= -20
The relationship between them
The value of -19
is 1 more than -20
arithmetically, and this follows a generic pattern in which any value of -n
in two's complement is always one more than the value of ~n
, meaning the following always holds true for a value n
:
-n = ~n + 1
~n = -n - 1 = -(n + 1)
This means that you can simply look at the 5-bit value 15
, negate it and subtract 1 to get ~15
:
~15 = (-(15) - 1)
= -16
-16
for a 5-bit value in two's complement is represented as:
-16 8 4 2 1
1 0 0 0 0 = -16
Flipping the bits using the ~
operator yields the original value 15
:
-16 8 4 2 1
0 1 1 1 1 = ~(-16) = -(-16 + 1) = -(-15) = 15
Restrictions
I feel I should mention arithmetic overflow regarding two's complement. I'll use the example of a 2-bit signed integer to illustrate. There are 2^2=4 values for a 2-bit signed integer: -2, -1, 0, and 1. If you attempt to negate -2
, it won't work:
-2 1
1 0 = -2
Writing +2
in plain binary yields 1 0
, the same as the representation of -2
above. Because of this, +2
is not possible for a 2-bit signed integer. Using the equations above also reveals the same issue:
// Flip the bits to obtain the value of ~(-2)
~(-2) = -(-2 + 1)
~(-2) = 1
// Substitute 1 in place of ~(-2) to find the result of -(-2)
-(-2) = ~(-2) + 1
-(-2) = 1 + 1
-(-2) = 2
While this makes sense mathematically, the fact is that 2 is outside the representable range of values (only -2, -1, 0, and 1 are allowed). That is, adding 1 to 01 (1) results in 10 (-2). There's no way to magically add an extra bit in hardware to yield a new sign bit position, so instead you get an arithmetic overflow.
In more general terms, you cannot negate an integer in which only the sign bit is set with a two's complement representation of signed integers. On the other hand, you cannot even represent a value like -2 in a 2-bit one's complement representation because you only have a sign bit, and the other bit represents the value 1
; you can only represent the values -1, -0, +0, and +1
with one's complement.