How are negative numbers represented -- sign-magnitude, ones' complement, two's complement
In this lecture you will understand: why representing negative numbers in computers is not as simple as "adding a minus sign", and how two's complement cleverly solves all the problems.
Life-based analogy: hotel floor numbering
Imagine a hotel with 256 floors.
If numbered with natural numbers, you can only use 0 to 255.
But what if we want to number from "128 floors underground" to "127 floors above ground"?
A clever approach: reinterpret the numbers 128~255 as -128~-1.
Number 128 becomes -128, number 255 becomes -1, and number 0 remains 0.
This isCore idea of two's complement— Within a finite bit range, reinterpret the "latter half" of the value space as negative numbers.
The simplest idea: sign-magnitude (original code)
The most direct idea for representing negative numbers: use the highest bit asSign bit。
- Highest bit 0 = positive number
- Highest bit 1 = negative number
- Remaining bits represent absolute value
Taking 8 bits as an example:
Seems reasonable, right? But sign-magnitude has two fatal problems.
Problem one: +0 and -0 both exist.
Positive 0 is00000000, negative 0 is10000000。
The same value has two representations.
This means every comparison operation (checking whether two numbers are equal) requires the hardware to additionally handle this special case.
Problem 2: The adder cannot do subtraction
Calculate using sign-magnitude5 + (-3):
5 + (-3) should equal 2, but direct addition using sign-magnitude gives -8.
This means the CPU must separately design a subtraction circuit instead of reusing the existing addition circuit.
The problem with sign-magnitude can be summarized as: intuitive but not practical. It is suitable for humans to read, but not suitable for circuit implementation.
The clock's revelation: modular arithmetic
Before continuing, we need to understand a key concept—Modulo operation。
Look at a clock: the face has only 12 markings.
Starting from 10 o'clock, you want to adjust to 7 o'clock. There are two ways:
Method one (counterclockwise): 10 - 3 =7(Subtraction)
Method 2 (clockwise): 10 + 9 = 19, 19 mod 12 =7(Addition)
In a "modulo 12" system, -3 and +9 are equivalent (because -3 + 12 = 9).
More generally:In a modulo M system, subtracting a number is equivalent to adding (M - that number)。
Two's complement applies this idea to binary. For 8-bit binary, the modulus is 2^8 = 256.
The two's complement of -3 = 256 - 3 = 253 = binary.11111101。
therefore5 + (-3)Can become5 + 253 = 258。
But in an 8-bit system, 258 exceeds the upper limit of 255. The carry from the highest bit "overflows" and is discarded, leaving258 mod 256 = 2。
Addition obtained the correct result of subtraction.
Two's complement ring diagram (interactive demo)
The ring diagram below intuitively shows a 4-bit two's complement system.
4 bits can represent 16 values (0~15); two's complement reinterprets them as -8~+7.
Drag the slider to change the value and observe the pointer's position on the ring.
Two's complement ring diagram (4-bit system, range -8 ~ +7)
Binary representation:0000| Unsigned interpretation:0
Plotly two's complement ring chart (scatterpolar polar coordinates).
The figure below uses Plotly.js polar coordinates to display the 16 ticks of 4-bit two's complement. Blue = positive region, red = negative region, green = zero point.
Meaning of cyclic:
- Clockwise direction = value increases
- Going around one circle (exceeding the maximum) = returning to the starting point (the "overflow" effect of modular arithmetic)
- Red region = negative numbers (two's complement interpretation), blue region = positive numbers
- The inner ring is labeled with unsigned interpretation (corresponding to 0~15), the outer ring is labeled with two's complement interpretation (-8~+7)
From the diagram, we can see that,Two's complement essentially folds the value space into a ring.。
When positive numbers reach the end (after +7), they naturally become negative (-8). Subtraction and addition are the same direction on the ring.
Two's complement calculation rules
To manually compute a number's two's complement, there are two equivalent quick methods:
Method one: invert and add 1 (standard method)
- Write the binary corresponding to the absolute value
- Invert each bit (0 becomes 1, 1 becomes 0) to get the ones' complement
- Add 1 to the one's complement to get the two's complement
Method two: from right to left (quick calculation method)
- Find the first 1 from right to left
- Invert all bits to the left of this 1
- This 1 and all bits to its right remain unchanged
Take -20 as an example (8-bit system), verify with both methods:
Verification: the two's complement of -20 is 11101100; interpreted as unsigned, it equals 236.
236 + 20 = 256, exactly one modulus. This shows the calculation is correct.
Comparison of the three representations
| value | sign-magnitude | ones' complement | two's complement |
|---|---|---|---|
| +5 | 00000101 | 00000101 | 00000101 |
| -5 | 10000101 | 11111010 | 11111011 |
| +0 | 00000000 | 00000000 | 00000000 |
| -0 | 10000000 | 11111111 | Does not exist |
| +127 | 01111111 | 01111111 | 01111111 |
| -127 | 11111111 | 10000000 | 10000001 |
| -128 | Cannot represent | Cannot represent | 10000000 |
Note three key differences:
- Two's complement has no -0: All 1s are no longer wasted on -0, but instead represent -1.
- Two's complement has one extra negative number8-bit two's complement range is -128 ~ +127, which has one more -128 than the sign-magnitude range of -127 ~ +127.
- Two's complement unifies addition: Adding positive and negative numbers uses the same circuit.
This is why Java's
byteThe type range is -128~127, and C language'ssigned charis also -128~127. They both use 8-bit two's complement.
Interactive demo: two's complement calculator
Below, enter an integer in the 8-bit range (-128 ~ 127) to view its sign-magnitude, ones' complement, and two's complement.
Two's complement calculator (8-bit)
Special note: when -128 is entered, the two's complement is10000000。
The absolute value of this number is 128, but 128 is outside the 8-bit positive range (0~127).
So -128 has no corresponding positive representation—this is an interesting property of the two's complement system.
Two's complement overflow
8-bit two's complement range is -128~127.
If the calculation result exceeds this range, it will causeoverflow。
This again echoes the earlier ring diagram—after reaching the end, the value "wraps around".
In C/C++, overflow of signed integers isUndefined behavior(the compiler may assume it never happens), which is the root of many strange bugs.
Code demo: complete implementation of two's complement
Example
def to_twos_complement(n, bits=8):
Convert integer n to a two's complement string with the specified bit width
if n < -2**(bits-1) or n > 2**(bits-1) - 1:
raise ValueError(fValue {n} is out of the {bits}-bit two's complement range [-{2**(bits-1)}, {2**(bits-1)-1}])
# Using modulo arithmetic: the unsigned equivalent of a negative number n = n + 2^bits
# Using bitmask directly in Python
mask = (1 << bits) - 1 # For example, 8 bits: 0xFF = 255
unsigned_val = n & mask
return format(unsigned_val, f'0{bits}b')
def from_twos_complement(bin_str):
"""Convert a two's complement string back to a decimal integer."""
bits = len(bin_str)
unsigned_val = int(bin_str, 2)
# If the highest bit is 1 (negative), subtract 2^bits
if bin_str[0] == '1':
return unsigned_val - (1 << bits)
else:
return unsigned_val
def show_conversion_steps(n, bits=8):
"""Show the detailed steps from n to two's complement."""
abs_bin = format(abs(n), f'0{bits}b')
print(f"Value: {n}")
print(fStep 1: absolute value |{n}| = {abs(n)} → binary = {abs_bin})
if n >= 0:
print(f" Step 2: the two's complement of a positive number = sign-magnitude = {abs_bin}")
else:
# Invert
inverted = ''.join('1' if b == '0' else '0' for b in abs_bin)
print(fStep 2: Invert (one's complement) = {inverted})
# add 1
carry = 1
result_list = list(inverted)
for i in range(bits - 1, -1, -1):
if carry == 0:
break
total = int(result_list[i]) + carry
result_list[i] = str(total % 2)
carry = total // 2
result = ''.join(result_list)
print(fStep 3: one's complement + 1 = {result})
comp = to_twos_complement(n, bits)
print(f" → Final two's complement: {comp}")
# Verify
recovered = from_twos_complement(comp)
print(fVerification: two's complement {comp} → interpreted as {recovered})
print()
# Test
print("=" * 50)
print("Two's complement conversion demo (8-bit system)")
print("=" * 50)
print()
for val in [5, -5, 0, -1, 127, -128]:
show_conversion_steps(val)
# Verify two's complement addition
print("=" * 50)
print("Two's complement addition verification: subtraction becomes addition")
print("=" * 50)
def twos_add(a, b, bits=8):
"""Two's complement addition (including overflow handling)"""
mask = (1 << bits) - 1
result_unsigned = (a + b) & mask
# Interpret Signed Result
if result_unsigned >= (1 << (bits - 1)):
signed_result = result_unsigned - (1 << bits)
else:
signed_result = result_unsigned
return signed_result
# 5 - 3 = 2
print(f5 + (-3) = {twos_add(5, -3)} (expected: 2))
# 127 + 1 = -128 (overflow)
print(f127 + 1 = {twos_add(127, 1)} (expected: -128, overflow occurred))
# -128 - 1 = 127 (overflow)
print(f-128 + (-1) = {twos_add(-128, -1)} (expected: 127, overflow occurred))
# add zero
print(f0 + 0 = {twos_add(0, 0)} (expected: 0))
# Verify with EXAMPLE characters: R=82, U=85 → 82 + (-85) = -3
print(f"\nEXAMPLE Test:R(82) + (-U(85)) = {twos_add(82, -85)} (period望: -3)")
# Demonstrating the cyclic nature of two's complement
print("\nTwo's complement ring (4-bit simplified version, range -8 to +7):)
print("Value | Two's complement | Unsigned")
print("-" * 30)
for n in range(-8, 8):
comp = to_twos_complement(n, 4)
unsig = int(comp, 2)
print(f" {n:3d} | {comp} | {unsig:2d}")