Home

Two's Complement: How Computers Represent Negative Numbers

In computer architecture, 2’s complement is the standard method for representing signed integers (both positive and negative numbers). It’s elegant because it allows the CPU to perform subtraction using the exact same hardware logic as addition.


The Core Concept

In a fixed-width binary system (like 8-bit or 16-bit), the most significant bit (MSB)—the bit furthest to the left—acts as the sign bit:

MSB Value Number Sign
0 Positive
1 Negative

This means the same bit pattern can represent different values depending on whether we interpret it as signed or unsigned.

Example: The Byte 11111111

Interpretation Value
Unsigned 255
Signed (8-bit) -1

Identifying the Sign of a Number

Whether a binary number is positive or negative depends entirely on how the computer is interpreting that data.

1. Data Type Context

Type MSB Meaning
Unsigned MSB is part of the magnitude (always positive)
Signed MSB indicates the sign

2. Fixed Width Matters

You must know the bit-width to know which bit is the “leftmost” one.

10000001 as 8-bit signed:  -127
10000001 as 16-bit signed: +129 (padded as 00000000 10000001)

C Language Data Types

In C, how you declare a variable determines how the CPU treats the MSB.

Type Range Description
signed char -128 to 127 8-bit, MSB is sign
unsigned char 0 to 255 8-bit, all magnitude
signed int -2,147,483,648 to 2,147,483,647 32-bit, MSB is sign
unsigned int 0 to 4,294,967,295 32-bit, all magnitude

The “Wrap Around” Effect

Because 2’s complement uses one bit for the sign, the maximum positive value is roughly half that of an unsigned variable. If you have a signed char at 127 (01111111) and add 1:

01111111  (127)
+       1
─────────
10000000  (-128 in signed, 128 in unsigned)

The CPU interprets 10000000 as -128 in signed context!


Converting to 2’s Complement

To represent a negative number (e.g., converting +5 to -5):

Step-by-Step: 5 → -5

Step Action Result
1 Start with positive binary 00000101
2 Invert all bits (1’s complement) 11111010
3 Add 1 11111011

Final: 11111011 represents -5 in 8-bit 2’s complement.

Why This Works

The inversion and addition effectively calculates 2⁸ - 5 = 256 - 5 = 251, which is 11111011 in binary. When interpreted as signed, this equals -5.


Reading Negative 2’s Complement Values

To find the decimal value of a negative 2’s complement number, use a weighted sum where the MSB has a negative weight.

For an 8-bit number, the MSB weight is -128.

Example: Reading 11111011

Position:   -128  64   32   16    8    4    2    1
Binary:       1    1    1    1    1    0    1    1
Weighted:  -128 + 64 + 32 + 16 +  8 +  0 +  2 +  1
= -128 + 123 = -5

Calculation:

(-128 × 1) + (64 × 1) + (32 × 1) + (16 × 1) + (8 × 1) + (4 × 0) + (2 × 1) + (1 × 1)
= -128 + 64 + 32 + 16 + 8 + 0 + 2 + 1
= -128 + 123
= -5

Why Computers Use This System

1. One Zero

There’s only one representation for zero (00000000). Other signed number representations have both +0 and -0, which complicates hardware.

2. Unified Arithmetic

To subtract B from A, the computer calculates A + (-B). This means:

Example: 7 - 5

The CPU calculates: 7 + (-5)

  00000111  (7)
+ 11111011  (-5 in 2's complement)
─────────
 00000010  (2) ← The carry bit is discarded!

Practical Examples

C: Signed vs Unsigned

#include <stdio.h>

int main() {
    unsigned char u_val = 255; // 11111111 in binary
    signed char s_val = 255;   // Also 11111111 in binary

    // u_val treats the MSB as 128, total 255
    printf("Unsigned char: %u\n", u_val);  // Output: 255

    // s_val treats the MSB as -128, total -1
    printf("Signed char: %d\n", s_val);    // Output: -1

    return 0;
}

Compile and run:

gcc limits.c -o limits
./limits

Python: Simulating 8-bit Behavior

Python normally has arbitrary precision, so we need to manually implement 2’s complement logic:

def interpret_as_8bit_signed(byte_val):
    """
    Interpret an 8-bit value as signed.
    If the MSB (bit 7) is set, the value is negative.
    """
    # Check if the 8th bit (MSB) is set using bitwise AND
    if byte_val & 0x80:
        # It's negative: subtract 2^8 (256) to get the 2's complement value
        return byte_val - 256
    else:
        # It's positive
        return byte_val

# The binary pattern 11111111 (255 in decimal)
binary_pattern = 0b11111111

print(f"Binary pattern: {bin(binary_pattern)}")
print(f"Interpreted as Unsigned: {binary_pattern}")
print(f"Interpreted as Signed:   {interpret_as_8bit_signed(binary_pattern)}")

Output:

Binary pattern: 0b11111111
Interpreted as Unsigned: 255
Interpreted as Signed:   -1

Quick Reference Table

Binary (8-bit) Unsigned Signed
00000000 0 0
00000001 1 1
01111111 127 127
10000000 128 -128
10000001 129 -127
11111110 254 -2
11111111 255 -1

Key Takeaways

Concept Rule
MSB = 0 Number is positive
MSB = 1, unsigned Number is positive (large)
MSB = 1, signed Number is negative
Convert to negative Invert bits, add 1
Read negative MSB has negative weight (-128 for 8-bit)
Zero Only one representation: all zeros

Practice Exercise

Try converting these yourself:

  1. +8 to -8 in 8-bit 2’s complement

    • Start: 00001000
    • Invert: 11110111
    • Add 1: 11111000
  2. What decimal value is 10110000 as signed?

    • MSB is 1 → negative
    • Weighted sum: -128 + 32 + 16 = -80
Tags: CPythonBinaryMemoryLow-Level