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...
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
| Decimal | 8-bit binary | Hex |
|---|---|---|
| 127 | 0111 1111 | 7F |
| 1 | 0000 0001 | 01 |
| 0 | 0000 0000 | 00 |
| -1 | 1111 1111 | FF |
| -2 | 1111 1110 | FE |
| -127 | 1000 0001 | 81 |
| -128 | 1000 0000 | 80 |
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:
| Bits | Min | Max | Type examples |
|---|---|---|---|
| 8 | -128 | 127 | int8_t, sbyte |
| 16 | -32,768 | 32,767 | int16_t, short |
| 32 | -2,147,483,648 | 2,147,483,647 | int, int32_t |
| 64 | -9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 | long, int64_t |
Unsigned integers use all bits for magnitude:
| Bits | Min | Max |
|---|---|---|
| 8 | 0 | 255 |
| 16 | 0 | 65,535 |
| 32 | 0 | 4,294,967,295 |
| 64 | 0 | 18,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 tools
- Number Base Converter — convert between binary, decimal, hex
- Binary to Decimal — understanding binary representation
- Hexadecimal Guide — hex as compact binary notation
Related posts
- Binary Arithmetic — Addition, Subtraction, and Two's Complement — Learn how computers perform binary arithmetic: binary addition with carry, two's…
- Binary to Decimal — Convert Binary Numbers the Right Way — Binary to decimal conversion is foundational to understanding how computers stor…
- Binary to Text: How Binary Numbers Represent Characters — Binary to text conversion isn't magic — it's a lookup table. ASCII, Unicode, UTF…
- Decimal to Binary — How to Convert Numbers Between Bases — Decimal to binary, binary to decimal, hex to binary — number base conversion exp…
- Octal Converter — Convert Between Octal, Decimal, Binary, and Hex — Octal is base-8, using digits 0–7. It's used in Unix file permissions and legacy…
Related tool
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.