Recommended resources
The freely available resources this lesson will direct you to.
Hello! In the previous lesson, you saw that a fixed number of bits limits the values an unsigned integer can hold. Now we’ll keep the same bit patterns but interpret them as signed integers, including negative values. This matters whenever you inspect a value in memory or a hex dump: the bits alone do not tell you whether 11111111 means 255 or −1.
By the end of this lesson, you’ll be able to encode and decode fixed-width two’s-complement integers, determine their range, and handle the unusual value at the negative end of that range.
Why the interpretation matters
Suppose we have four bits. Unsigned 0101 means 5. We could try to represent −5 by changing only the leftmost bit, but ordinary binary addition would then give the wrong answer for . Two’s complement assigns negative values to bit patterns in a way that lets the usual binary addition circuitry remain useful.
Watch the selected parts of Ben Eater’s Twos complement: Negative numbers in binary. The opening shows why a simple sign-bit scheme is insufficient; the later portions introduce two’s complement, its negative-weight bit, and the procedure for changing a number’s sign.
Twos complement: Negative numbers in binary
Ben Eater’s video develops two’s complement from an arithmetic problem, rather than presenting it as a rule to memorize.
Watch the failed approach for the four-bit sign-bit example. Skip ahead to two’s complement and its addition examples. Then watch negative place value and changing sign. Focus on why the leftmost bit does more than merely label the number “negative.”
For four-bit two’s complement, the place values are −8, 4, 2, and 1. The leftmost bit still tells you the sign: a leading zero gives a non-negative value, while a leading one gives a negative value. But treating that bit as a negative place value lets you calculate the value directly. For example,
The outer ring of the following diagram lists the same four-bit patterns under three different interpretations. Follow a single slice inward to see why the interpretation must be specified alongside the bits.

Notice that the orange ring has both zero and negative zero. Two’s complement has just one zero, 0000, and uses the remaining pattern for an extra negative value. We will use the turquoise ring’s interpretation from here on.
The signed range and two ways to decode
With bits, two’s complement still has distinct patterns. Half begin with zero. They represent zero through . The other half represent through −1. Thus the range is
| Width | Smallest signed value | Largest signed value |
|---|---|---|
| 4 bits | −8 | 7 |
| 8 bits | −128 | 127 |
| 16 bits | −32,768 | 32,767 |
There is one more negative value than positive value because zero occupies a pattern on the non-negative side.
To decode a pattern, you can add its place values, making only the leftmost weight negative. For eight-bit 11011000, those weights give
An alternative builds on your unsigned work. First read the bits as an unsigned value . If the leading bit is zero, that is also the signed value. If it is one, subtract :
For 11011000, the unsigned value is 216, so its eight-bit signed value is . These are two calculations of the same interpretation, not two different encodings.
Read the opening explanation and worked examples in Runestone Academy’s Negative Values – Two’s Complement. It offers a third way to recognize the magnitude of a negative pattern.
4.6. Negative Values - Two’s Complement — Welcome To CS
Runestone Academy’s explanation reinforces how to read negative bit patterns, including an eight-bit example.
In Section 4.6, “Negative Values – Two’s Complement,” start at “Rather than design new rules for doing math.” Read the interpretation examples through the eight-bit 11011000 example, stopping before “Advantages of Two's Complement.” Pay particular attention to “Two’s Complement Interpretation”: for a leading one, it flips the bits after the leading bit and adds one to their value to obtain the magnitude. Compare its result of −40 with the place-value calculation above.
There is no conflict between that reading’s decoding shortcut and the video’s “flip all bits, then add one” procedure. If 1011 is negative, flipping all four bits gives 0100; adding one gives 0101, or 5, so the original means −5. Equivalently, keep the leading one as the sign indicator, flip the remaining 011 to 100 (4), and add one to get a magnitude of 5.
Encoding a signed integer
Always choose the width first and check that the value fits its signed range. For a non-negative value that fits, write its ordinary binary representation and pad on the left with zeros. Eight-bit is 00001101.
For a negative value whose positive counterpart fits, a convenient procedure is:
- Write its positive magnitude in the chosen width.
- Flip every bit within that width.
- Add one, retaining only the chosen number of bits.
To encode −13 in eight bits:
00001101 +13
11110010 flip all eight bits
11110011 add one: −13
Check by decoding: 11110011 is unsigned 243, and . Grouping the bits into pairs of four also gives F3 in hexadecimal, so an eight-bit signed 0xF3 is −13. Hexadecimal makes the pattern shorter; it does not specify whether that pattern is signed.
There is also a method that covers every representable negative value directly. For a negative integer , calculate , then write that non-negative result in exactly bits. For eight-bit −13, , again giving 11110011.
This direct method is particularly helpful at the lower endpoint. Eight-bit −128 is 10000000, because . You cannot start the flip-and-add-one procedure from positive 128 in eight-bit two’s complement: positive 128 does not fit. Likewise, negating the eight-bit pattern 10000000 within eight bits returns 10000000, not positive 128. The asymmetric range explains why.
Width is part of the answer. Four-bit 1011 represents −5, whereas eight-bit 00001011 represents 11. To preserve −5 while increasing its width, extend its leading sign bit: eight-bit 11111011 still represents −5.
Check the mapping with Python
Python’s ordinary integers are not restricted to eight bits, so the following code models a fixed-width representation. It checks that a signed value fits before encoding it, and it requires a bit pattern to fit the stated width before decoding it.
def encode_signed(value, width):
half = 1 << (width - 1)
if not -half <= value < half:
raise ValueError("value does not fit this signed width")
return value % (1 << width)
def decode_signed(bits, width):
modulus = 1 << width
if not 0 <= bits < modulus:
raise ValueError("bit pattern does not fit this width")
if bits >= (1 << (width - 1)):
return bits - modulus
return bits
width = 8
for value in (-128, -40, -13, -1, 0, 7, 127):
bits = encode_signed(value, width)
print(f"{value:4} {bits:0{width}b} {decode_signed(bits, width):4}")
The first and last columns should agree on every line. In the middle column, notice that all the negative values begin with 1 and all the non-negative values begin with 0. Changing width to 4 will cause the range check to reject several listed values; that is the intended behavior.
One final distinction from the previous lesson: discarding a carry beyond the width is not, by itself, a test for signed overflow. Four-bit 0101 plus 1011 produces 0000 after a carry is discarded, correctly representing . But 0111 plus 0001 produces 1000, which represents −8 rather than the mathematical answer 8, without a carry beyond the fourth bit. Before trusting signed arithmetic, the mathematical result must fit the signed range. In particular, do not assume that signed overflow in C is guaranteed to wrap.
Key takeaways
Two’s complement gives the leftmost bit a negative place value. An -bit signed integer ranges from to . To decode a pattern with a leading one, you can use its signed place values or subtract from its unsigned value. To encode a negative value, check the range, then use flip-and-add-one when its positive counterpart fits, or calculate directly.
Next, you’ll work with bits more selectively: using bitwise operations and masks to test or change particular positions without replacing the entire value.