2’s Complement
The dominant way modern hardware represents signed integers.
Core Idea
For an N-bit integer, a negative number -x is stored as 2^N - x.
For 8-bit (N=8):
-1 → 2^8 - 1 = 255 = 0xFF = 11111111
-2 → 2^8 - 2 = 254 = 0xFE = 11111110
-128 → 2^8 - 128 = 128 = 0x80 = 10000000
Bit Layout (8-bit)
Bit pattern | Unsigned | Signed (2's complement)
-------------|----------|------------------------
0000 0000 | 0 | 0
0000 0001 | 1 | 1
0111 1111 | 127 | 127 ← INT_MAX
1000 0000 | 128 | -128 ← INT_MIN (sign bit flips)
1000 0001 | 129 | -127
1111 1110 | 254 | -2
1111 1111 | 255 | -1
The sign bit (MSB) being 1 means negative. The range is asymmetric: one more negative value than positive.
How to Negate Manually
Two equivalent methods:
Method 1: Flip all bits, then add 1
5 = 0000 0101
~5 = 1111 1010 (flip)
-5 = 1111 1011 (add 1)
Method 2: 2^N - x
-5 = 256 - 5 = 251 = 1111 0101 ✓ (same result)
Why Hardware Loves It
Addition and subtraction use the same circuit for signed and unsigned:
0000 0101 (+5)
+ 1111 1011 (-5 in 2's complement)
-----------
1 0000 0000 → carry discarded → 0000 0000 = 0 ✓
No special subtraction hardware needed. This is the primary reason 2’s complement won over alternatives like sign-magnitude or 1’s complement.
The Three Historical Alternatives (Mostly Dead)
| Scheme | How -5 looks (8-bit) | Problem |
|---|---|---|
| Sign-magnitude | 1000 0101 | Two zeros (+0 and -0), complex arithmetic |
| 1’s complement | 1111 1010 | Also two zeros, end-around carry needed |
| 2’s complement | 1111 1011 | One zero, simple arithmetic ✓ |
Reading a Bit Pattern
Example: 1111 1111
Method 1 — sign bit formula (MSB has weight -2^(N-1), rest are normal):
1111 1111
│└──────┘
│ positional values: 64+32+16+8+4+2+1 = 127
│
└─ sign bit: -128
Total: -128 + 127 = -1
Method 2 — flip and add 1:
1111 1111 → flip → 0000 0000 → add 1 → 0000 0001 = 1
Magnitude is 1, sign bit is 1, so the value is -1.
Verify:
1111 1111 (-1)
+ 0000 0001 (+1)
-----------
1 0000 0000 → carry dropped → 0 ✓
All-ones is always
-1in 2’s complement, regardless of bit width (8, 16, 32, 64).
Example: 1000 0000
Method 1 — sign bit formula:
1000 0000
│└──────┘
│ positional values: 0+0+0+0+0+0+0 = 0
│
└─ sign bit: -128
Total: -128 + 0 = -128
Method 2 — flip and add 1:
1000 0000 → flip → 0111 1111 → add 1 → 1000 0000
You get 1000 0000 back — it’s its own negation. This is why -128 has no positive counterpart in 8-bit signed: +128 doesn’t fit (0111 1111 = 127 is the max).
The asymmetry:
INT_MIN = -128 = 1000 0000
INT_MAX = +127 = 0111 1111
|INT_MIN| > INT_MAX — this is why abs(INT_MIN) is undefined behavior in C. Negating -128 would require +128, which overflows.
Same Bits, Different Meaning
1000 0000 represents different values depending on interpretation:
| Interpretation | Value |
|---|---|
| Unsigned | 128 |
| 2’s complement signed | -128 |
The hardware stores 1000 0000 — whether that’s 128 or -128 is decided purely by how your code declares the variable:
uint8_t u = 0x80; // 128
int8_t s = 0x80; // -128
Casting uint32_t to int32_t
When you cast uint32_t v to int32_t:
- The bit pattern does not change
- The CPU just reinterprets bit 31 as a sign bit
- If bit 31 is
1(value ≥2^31), the result is negative
uint32_t v = 0x80000000; // 2147483648, bit 31 set
int32_t s = (int32_t)v; // -2147483648 — same bits, signed interpretation
In C11/C17 this is implementation-defined behavior. In C23 it is finally guaranteed by the standard, as 2’s complement is now mandated for all signed integer types.
Overflow Wraps the Number Line into a Circle
Visualize it as a clock:
0
-1 1
-2 2
...
-128 127 (8-bit)
-127
Adding past INT_MAX wraps to INT_MIN, and vice versa:
result = (a + b) mod 2^N (then reinterpret as signed)