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:

+5 =
0
0
0
0
0
1
0
1
Sign bit 0 = positive
-5 =
1
0
0
0
0
1
0
1
Sign bit 1 = negative

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 (sign-magnitude)
00000101
+
-3 (sign-magnitude)
10000011
=
Add directly
10001000
=
Result
-8

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)

0

Binary representation:0000| Unsigned interpretation:0

Load the Plotly.js library to draw polar donut chart

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.

Positive range (0 to +7)
Negative number region (-8 to -1)
zero point
Overflow point (+7→-8)

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)

  1. Write the binary corresponding to the absolute value
  2. Invert each bit (0 becomes 1, 1 becomes 0) to get the ones' complement
  3. Add 1 to the one's complement to get the two's complement

Method two: from right to left (quick calculation method)

  1. Find the first 1 from right to left
  2. Invert all bits to the left of this 1
  3. This 1 and all bits to its right remain unchanged

Take -20 as an example (8-bit system), verify with both methods:

20 in binary
00010100
→
Invert (ones' complement)
11101011
→
Add 1 (two's complement)
11101100

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

valuesign-magnitudeones' complementtwo's complement
+5000001010000010100000101
-5100001011111101011111011
+0000000000000000000000000
-01000000011111111Does not exist
+127011111110111111101111111
-127111111111000000010000001
-128Cannot representCannot represent10000000

Note three key differences:

  1. Two's complement has no -0: All 1s are no longer wasted on -0, but instead represent -1.
  2. 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.
  3. Two's complement unifies addition: Adding positive and negative numbers uses the same circuit.

This is why Java'sbyteThe 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)

Input integer:

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。

127 + 1
-128
(Positive overflow, negative result)
|
-128 - 1
+127
(Negative overflow, positive result)

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

# Complete implementation and verification of two's complement (example demo)

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}")
other extensions