X Xerobit

Two's Complement — How Computers Represent Negative Numbers

Two's complement is the standard way CPUs store negative integers. Understanding it explains sign overflow, bit masking, and why -1 in binary is all ones. Here's the math and...

Mian Ali Khalid · · 6 min read
Use the tool
Number Base Converter
Convert between binary, octal, decimal, hexadecimal, and text (UTF-8). Handles arbitrary lengths. Per-byte and per-character views.
Open Number Base Converter →

Two’s complement is the method used by virtually all modern CPUs to represent negative integers in binary. A 32-bit int can hold values from -2,147,483,648 to +2,147,483,647 — and the reason that range is exactly what it is comes directly from two’s complement.

Use the Number Base Converter to convert between decimal, binary, hex, and see two’s complement representations.

Why two’s complement?

Before two’s complement, systems tried other approaches:

Sign-magnitude: Use the MSB (most significant bit) as a sign flag. 0111 = +7, 1111 = -7. Problem: two representations of zero (0000 and 1000). Addition circuits can’t reuse for subtraction.

One’s complement: Flip all bits for negative. 0111 = +7, 1000 = -7. Still two zeros. Addition still needs special handling.

Two’s complement: Flip all bits and add 1. 0111 = +7, 1001 = -7. One zero. Regular addition handles subtraction automatically. This is why CPUs can use the same adder circuit for both addition and subtraction.

Computing two’s complement

Step 1: Flip all bits (one’s complement)
Step 2: Add 1

Convert +7 to -7 in 8-bit:

+7  = 00000111
Step 1 (flip): 11111000
Step 2 (+1):   11111001

-7 in 8-bit two's complement = 11111001

Verify: 11111001 → flip → 00000110 → add 1 → 00000111 = 7. Negate twice returns original.

Key values to know

Decimal8-bit binaryHex
1270111 11117F
10000 000101
00000 000000
-11111 1111FF
-21111 1110FE
-1271000 000181
-1281000 000080

Why -1 is all ones: Start with 1 = 00000001. Flip → 11111110. Add 1 → 11111111. All ones is -1. This is why 0xFFFFFFFF as a signed 32-bit int is -1.

Integer ranges by bit width

Two’s complement signed integer ranges:

BitsMinMaxType examples
8-128127int8_t, sbyte
16-32,76832,767int16_t, short
32-2,147,483,6482,147,483,647int, int32_t
64-9,223,372,036,854,775,8089,223,372,036,854,775,807long, int64_t

Unsigned integers use all bits for magnitude:

BitsMinMax
80255
16065,535
3204,294,967,295
64018,446,744,073,709,551,615

Sign overflow

When a signed integer exceeds its maximum value, it wraps around to the most negative value:

int8_t x = 127;
x = x + 1;  // -128!  (overflow wraps)

// In binary:
// 127 = 01111111
// +1  = 00000001
// ---  --------
//      10000000 = -128 in two's complement

This is undefined behavior in C/C++ for signed integers — the compiler is allowed to assume it never happens and may optimize assuming no overflow. In Java and many other languages, signed overflow is defined to wrap.

// Java: defined to wrap
int x = Integer.MAX_VALUE; // 2,147,483,647
x = x + 1;                 // -2,147,483,648 (wraps)

Two’s complement in practice

Bit manipulation

Two’s complement makes bit operations on negative numbers work intuitively:

// Get the lowest set bit:
int lowest = n & (-n);
// -n in two's complement flips all bits and adds 1,
// so the bits below the lowest set bit flip, and the lowest set bit stays.

// Check if power of 2:
bool isPow2 = (n > 0) && ((n & (n - 1)) == 0);

// Toggle bit k:
n ^= (1 << k);

// Clear bit k:
n &= ~(1 << k);

Arithmetic right shift

Right-shifting a signed integer extends the sign bit:

-8 = 1111 1000
-8 >> 1 = 1111 1100 = -4 (not +124!)

This is the sign-extending arithmetic right shift. Most languages do this for signed types; unsigned types use logical right shift (fills with 0).

Understanding 0xFFFFFFFF

unsigned int u = 0xFFFFFFFF;  // 4,294,967,295
int s = 0xFFFFFFFF;           // -1

// Same bits, different interpretation
printf("%u\n", u);  // 4294967295
printf("%d\n", s);  // -1

Context (signed vs unsigned type) determines interpretation.

Converting in code

def to_twos_complement(value, bits=8):
    if value < 0:
        value = (1 << bits) + value
    binary = format(value, f'0{bits}b')
    return binary

def from_twos_complement(binary):
    bits = len(binary)
    value = int(binary, 2)
    if binary[0] == '1':  # Negative number (MSB set)
        value -= (1 << bits)
    return value

print(to_twos_complement(-7, 8))    # '11111001'
print(from_twos_complement('11111001'))  # -7
// JavaScript uses 32-bit two's complement for bitwise operators:
(-7 >>> 0).toString(2);  // '11111111111111111111111111111001'
(-7 | 0);                // -7 (coerce to signed 32-bit)
(0xFFFFFFFF | 0);        // -1 (interpret as signed)

Related posts

Related tool

Number Base Converter

Convert between binary, octal, decimal, hexadecimal, and text (UTF-8). Handles arbitrary lengths. Per-byte and per-character views.

Written by Mian Ali Khalid. Part of the Encoding & Crypto pillar.