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)

SchemeHow -5 looks (8-bit)Problem
Sign-magnitude1000 0101Two zeros (+0 and -0), complex arithmetic
1’s complement1111 1010Also two zeros, end-around carry needed
2’s complement1111 1011One 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 -1 in 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:

InterpretationValue
Unsigned128
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)