↓ Skip to main content

How the Computer Subtracts by Adding: Two's Complement and Overflow

 Author
Author
Francisco Bustamante
Chemist, data scientist and Python programmer.
Table of Contents
From Zero to Float - This article is part of a series.
Part 3: This Article

In the previous article, we learned to add and subtract integers the way we do on paper, and we ended with a question: does the hardware do the same, borrowing from neighboring bits? And what if I told you that the computer, in fact, does not subtract at all?

As we saw at the end of that article, a subtractor circuit costs about the same as an adder; what makes no sense is keeping the two side by side doing almost identical work. For a single circuit to get the job done, computer engineering developed a method to turn subtractions into additions. Instead of computing \(A - B\), the computer computes \(A + (-B)\).

But how do we represent a negative number using only zeros and ones? If we have just two symbols, where do we put the “minus” sign? This is where Two’s Complement comes in, the fundamental basis of integer arithmetic in modern processors.

The Idea of the Complement
#

Before thinking about bits, let us analyze the problem in our ordinary decimal base. Imagine a very simple calculator with a display of only 4 digits. The largest number it can show is 9999.

If we add 1 to that value (9999 + 1), the mathematical result would be 10000. But since the display only has room for 4 digits, the leftmost 1 disappears (it overflows past the limit of the display), and the screen shows only 0000.

Now imagine the opposite path. If the screen shows 0000 and we try to subtract 1, the calculator wraps around the other way and shows 9999.

In this limited system, the number 9999 behaves mathematically as if it were -1. Shall we test it? If we add 5 to 9999 (our “fake -1”):

\[ 5 + 9999 = 10004 \]

Discarding the first digit, which does not fit on the display, leaves 0004. That is, \(5 + (-1) = 4\). The computation worked perfectly using only addition!

This is the Radix Complement: we use the “large” numbers at the top of the count to represent the negatives.

Let us now formalize this idea.

The complement of a number \(N\) in a given base \(b\) is the difference between the next power of \(b\) and the number, in that order: the power on top, the number below.

Complement in base 10

The base 10 complement of a number \(N\) is given by \(10^d - N\), where \(d\) is the number of digits of the system. Let us represent the equivalent of “-734” in a 3-digit system, the smallest one that holds 734:

$$ \begin{array}{r r r r} 1 & 0 & 0 & 0\\ -{} & 7 & 3 & 4\\ \hline &2 & 6 & 6 \end{array} $$

The number 266 is the “complement” of 734. Adding 266 will have the same effect as subtracting 734 (ignoring the overflow, that is, the carry that goes past the limit of the display).

The size of the system is a choice, and it must be respected from beginning to end: on the 4-digit display from the start of the article, the complement of 734 would be \(10^4 - 734 = 9266\), and it is 9266 that would behave as \(-734\) there. The reasoning is the same; only the number of places in the system changes. We will visualize this a little later in this article. First, we need to understand how negative numbers are represented in binary.

Signed Numbers in Binary
#

In the computer, we have a fixed number of bits (usually 8, 16, 32, or 64). To tell positives from negatives, we use the Most Significant Bit (the leftmost bit, called the MSB) as the Sign Bit.

  • A 0 at the start indicates a Positive number.
  • A 1 at the start indicates a Negative number.

So far, so good. The question that remains is: what do we do with the other bits? Reserving the MSB for the sign is only half of the decision, and historically there have been three different answers for the other half (signed number representations).

Scheme How it writes \(-5\) in 8 bits How many zeros exist Does the same adder work for adding and subtracting?
Sign-magnitude 10000101 (sign bit + the usual 5) two: 00000000 and 10000000 no
One’s complement 11111010 (flip the bits) two: 00000000 and 11111111 no, it needs an extra step in the addition
Two’s complement 11111011 (flip and add 1) just one yes

The first two are far more intuitive for a human (sign-magnitude is literally “write the sign and then the usual number”) and yet they lost the contest. The reason has a name: double zero. In both schemes, \(+0\) and \(-0\) are different bit patterns that mean exactly the same thing. This wastes one of the available combinations and, worse, forces the hardware to check two patterns every time someone writes if (x == 0). Add to that the extra circuitry both require in the addition, and the contest settles itself.

Two’s complement has a single zero, needs no adjustment when adding, and is what practically every processor uses today. It charges a price, which we will meet again later when discussing the limits of the representation: one combination is left over on the negative side, precisely the one that \(-0\) would occupy in the other schemes.

For example, in an 8-bit system:

  • 00000101 represents +5.
  • 11111011 represents -5 (in two’s complement).

Why is 11111011 equal to -5? Let us find out. Using positional notation:

Interpreting negative numbers in two’s complement

Let us interpret the binary number 11111011 as a signed integer (8 bits). Expanding:

$$ 1 \times 2^7 + 1 \times 2^6 + 1 \times 2^5 + 1 \times 2^4 + 1 \times 2^3 + 0 \times 2^2 + 1 \times 2^1 + 1 \times 2^0 $$

Computing the values:

$$ 128 + 64 + 32 + 16 + 8 + 0 + 2 + 1 = 251 $$

Since the leftmost bit is 1, we know the number is negative. To find its real value, we subtract \(256\) (which is \(2^8\), the next power of 2):

$$ 251 - 256 = -5 $$

Therefore, 11111011 represents -5 in two’s complement. This method works for any number of bits.

Another way is to consider positional notation with a negative sign bit:

$$ -1 \times 2^7 + 1 \times 2^6 + 1 \times 2^5 + 1 \times 2^4 + 1 \times 2^3 + 0 \times 2^2 + 1 \times 2^1 + 1 \times 2^0 $$

Computing the values:

$$ -128 + 64 + 32 + 16 + 8 + 0 + 2 + 1 = -5 $$

Both ways lead to the same result.

This second reading, the negative-weight one, becomes much more concrete when drawn. In the ruler below, the eight bits appear with the weight of each position on top and the corresponding term below, exactly as you would do with an unsigned number. The only difference is at the left end: that weight comes with its sign flipped, and that is all that separates \(251\) from \(-5\).

Ruler of the eight bits of 11111011 read as a signed integer. Above each bit is its weight: the first, highlighted in red, is worth minus 2 to the 7th power, that is, minus 128; the others are worth 64, 32, 16, 8, 4, 2, and 1. Below each bit, an arrow points to the corresponding term: minus 128, 64, 32, 16, 8, 0, 2, and 1. The sum of the terms is minus 5.
The same eight bits, with a single weight flipped in sign: it is the bit at the end that turns the integer negative.

Two’s Complement (Base 2)
#

The base 2 complement of a number \(N\) is mathematically given by \(2^d - N\).

Base 2 complement (Formal Mathematical Computation)

Let us find the negative of \(0101_2\) (5 in decimal) in a 4-bit system. The computation is \(2^4 - 5\), that is, \(10000_2 - 0101_2\).

Remember: in the formal algorithm already presented in this series of articles, when the top digit is smaller, we add the base (2) to it and add 1 to the bottom digit of the next column.

$$ \begin{array}{r r r r r r} & 1 & 0^{\scriptscriptstyle+2} & 0^{\scriptscriptstyle+2} & 0^{\scriptscriptstyle+2} & 0^{\scriptscriptstyle+2} \\ -& 0^{\scriptscriptstyle+1} & 0^{\scriptscriptstyle+1} & 1^{\scriptscriptstyle+1} & 0^{\scriptscriptstyle+1} & 1 \\ \hline & 0 & 1 & 0 & 1 & 1 \end{array} $$

Step by step:

  1. Col 0: \(0 < 1\). We add the base to the top (\(0+2=2\)). \(2-1=\mathbf{1}\). We compensate (+1) in the next one.
  2. Col 1: Top \(0\), Bottom \(0+1=1\). \(0 < 1\). We add the base (\(2\)). \(2-1=\mathbf{1}\). We compensate.
  3. Col 2: Top \(0\), Bottom \(1+1=2\). \(0 < 2\). We add the base (\(2\)). \(2-2=\mathbf{0}\). We compensate.
  4. Col 3: Top \(0\), Bottom \(0+1=1\). \(0 < 1\). We add the base (\(2\)). \(2-1=\mathbf{1}\). We compensate.
  5. Col 4: Top \(1\), Bottom \(0+1=1\). \(1-1=\mathbf{0}\).

Result: \(1011_2\) (the 4 useful bits).

Doing this subtraction is laborious. Fortunately, there is a much simpler algorithm used by hardware and by programmers: the “Flip and Add 1” technique.

Technique: Flip and Add 1
#

This technique is based on the One’s Complement (just flipping the bits) and then adding 1 to arrive at the Two’s Complement.

Let us find the two’s complement of the number 5 (0101) in a 4-bit system:

  1. Take the positive number: 0101
  2. Flip all the bits (0 becomes 1, 1 becomes 0): 1010
  3. Add 1 to the result.
Base 2 complement (Flip and Add 1)

After flipping the bits of 0101, we got 1010. Now, the work is just adding 1.

$$ \begin{array}{r r r r r} & 1 & 0 & 1 & 0 \\ +{} & & & & 1 \\ \hline & 1 & 0 & 1 & 1 \end{array} $$

The result 1011 represents -5 in Two’s Complement.

Why does adding 1 work?
#

You may be wondering: why does flipping the bits and adding 1 give exactly the mathematical complement (\(2^d - N\), in a system of \(d\) bits)?

The explanation lies in the relation between a sequence full of “ones” and the next power of 2:

  1. Flipping the bits is mathematically equivalent to subtracting the number from a sequence made only of ones (this is called the One’s Complement).
    • Example in 4 bits: Flipping \(x\) is equal to \(1111_2 - x\).
  2. The formal definition of Two’s Complement is to subtract from the next power of the base (\(10000_2\)).
    • That is: \(10000_2 - x\).

Since we know that \(10000_2\) is exactly \(1111_2 + 1\), we can substitute in the formula:

$$ \underbrace{(10000_2 - x)}_{\text{Formal Definition}} = \underbrace{(1111_2 + 1) - x}_{\text{Substitution}} = \underbrace{(1111_2 - x)}_{\text{Flipped Bits}} + 1 $$

In short: flipping the bits takes us “almost” there (up to the maximum value of the base minus 1). Adding 1 completes the difference to reach the correct power of 2. That is why the algorithm works perfectly.

Rule of Thumb (The Visual Shortcut)
#

For humans, there is an even faster way to do this “in your head”, with no computation:

Rule of thumb for two’s complement

To obtain the two’s complement of a number directly, scan the number from right to left, repeating the zero digits until you find the first digit 1, which must be kept. From there on, all the digits to the left of that first 1 must be flipped.

Examples of applying the rule:

  1. Number: \(011\mathbf{1}00\)

    • Keep the ending 100.
    • Flip the beginning 011 \(\to\) 100.
    • Result: \(100\mathbf{1}00\)
  2. Additional examples:

    • \(1010 \to 0110\)
    • \(11001 \to 00111\)
    • \(111000 \to 001000\)
    • \(1100110 \to 0011010\)

The Trick: Subtraction via Addition
#

Now that we know how to convert a number to negative (two’s complement), subtraction becomes trivial. To compute \(A - B\), the computer simply computes \(A + \text{Complement}(B)\).

Subtraction in base 10 using the radix complement

Subtraction \(913 - 734 = 179\)

The base 10 complement of 734 is \(1000 - 734 = 266\). Thus, the subtraction \(913 - 734\) can be turned into an addition: \(913 + 266 = 1179\).

Notice that the final result has one more digit than the original numbers (\(1\)). That digit is the carry that overflows the capacity of the representation. It is ignored to obtain the correct result (\(179\)).

Subtraction in base 2 using the radix complement

Let us compute \(25 - 19\) in binary (using 5 bits for simplicity). Then: \(25 = 11001_2\) and \(19 = 10011_2\)

We want to do: \(11001_2 - 10011_2\).

Step 1: Find the two’s complement of 19 (\(10011\)). Using the rule of thumb: keep the last 1, flip the rest to the left \(\to\) \(\mathbf{01101}\).

Step 2: Add 25 and (-19). We turn the subtraction into an addition: \(11001 + 01101\).

Writing the operation vertically, we have:

$$ \begin{array}{c c c c c c} {} & \scriptscriptstyle 1 & & & \scriptscriptstyle 1 & \\ {} & 1 & 1 & 0 & 0 & 1 \\ {} + & 0 & 1 & 1 & 0 & 1 \\ \hline 1) & 0 & 0 & 1 & 1 & 0 \end{array} $$

The “1)” bit on the left is the carry out of the number of bits. It is discarded. Final result: \(00110_2\) (which is 6 in decimal). Correct!

Limits of the Representation (Range)
#

Before we talk about computation errors, we need to understand the limits of the playing field. Since the number of bits is finite, there is a maximum and a minimum value we can write. If we try to go past those limits, the number “does not fit”.

The range of values depends on two factors:

  1. How many bits (\(N\)) we have available (8, 16, 32, 64…).
  2. Whether we are using Unsigned or Signed numbers.

Unsigned Numbers
#

Here, all the bits are used to represent the magnitude (the value) of the number. There are no negative numbers.

  • Minimum: It is always 0 (all bits zero).
  • Maximum: All bits are 1. The formula is \(2^N - 1\).

Example with 8 bits:

  • From \(0\) to \(2^8 - 1\)
  • From 0 to 255.

Signed Numbers (Two’s Complement)
#

Here, we split the possible combinations in half: half for the positives (and zero) and half for the negatives. The leftmost bit (MSB) defines the sign, leaving \(N-1\) bits for the value.

The formula changes slightly:

  • Minimum (Negative): \(-2^{N-1}\)
  • Maximum (Positive): \(2^{N-1} - 1\)

Why is the maximum one less than the absolute value of the minimum? Because zero takes up a place on the “positive” side (where the sign bit is 0). In 8 bits, we have 256 combinations:

  • 128 negative combinations (from -1 to -128).
  • 128 non-negative combinations (0 to +127).

There is an image that arranges everything we have seen so far in a single drawing, and it is worth keeping. Instead of thinking of the numbers as a line running from \(-8\) to \(+7\), think of them as a clock: the bit combinations laid out in a circle, 0000 at the top, adding 1 being one step clockwise. The figure uses 4 bits because 16 positions fit on the page; in 8 bits it is the same clock, just with 256 marks.

Two’s complement clock in 4 bits. The sixteen combinations are laid out in a circle, with the pattern 0000 at the top. Going down the right side, in blue, come 0001 to 0111, worth plus 1 to plus 7. Going up the left side, in red, come 1000 to 1111, worth minus 8 to minus 1. An arrow in the center indicates that adding 1 means turning one step clockwise. The passage between plus 7 and minus 8, at the bottom of the circle, is highlighted in yellow and labeled as the seam where overflow occurs. A note points out that 1111 and 0000 are neighbors, so that adding 1 to minus 1 gives zero.
The 16 combinations of 4 bits in a circle: adding 1 means turning one step clockwise, and the seam between +7 and −8 is where overflow happens.

The clock answers four questions at once that this article treats in separate sections:

  • Why the complement works. Walking 5 steps backward from zero lands in the same place as walking 11 forward, because the circle closes. That is why subtracting is adding the complement.
  • Why the range is asymmetric. There is only one mark for zero, at the top. Since the 16 positions split into two equal halves, one combination is left over on the negative side, and that is why we go from \(-8\) to \(+7\).
  • What overflow is. It is crossing the seam by accident: adding two positives and landing on the red side of the circle.
  • Why \(-8\) has no opposite. \(+8\) simply does not exist on the clock. The position where it would land is occupied by \(-8\) itself.

We will come back to each of these points later, but keep the drawing.

Reference Table
#

See how the range changes drastically depending on the interpretation:

Bits (\(N\)) Type Minimum Value Maximum Value
4 bits Unsigned 0 15
4 bits Signed -8 +7
8 bits Unsigned 0 255
8 bits Signed -128 +127
16 bits Unsigned 0 65,535
16 bits Signed -32,768 +32,767

Thus, if you have an 8-bit signed system and try to add \(100 + 30\), the mathematical result (130) is greater than the allowed limit (+127). The computer does not have enough bits to represent that value, causing what we call Overflow. We will understand this better later on. But first, an important detail.

The Minimum Value Paradox (A Dangerous “Gotcha”)
#

If you look at the table above carefully, you will notice an asymmetry: in 8 bits, we go from -128 to +127.

This creates a curious problem: the number -128 has no positive opposite. The value +128 simply does not fit in 8 signed bits.

This asymmetry is the price of the single zero. Since two’s complement does not spend a bit pattern on \(-0\), one combination is left over on the negative side, and that combination is \(-128\). In the schemes that lost the contest back there, the range was symmetric, but at the cost of having two zeros.

What happens if we apply the “Flip and Add 1” rule precisely to -128 (10000000)? Let us see:

  1. Original number: 10000000 (-128)
  2. Flip the bits: 01111111 (+127)
  3. Add 1:
$$ \begin{array}{r r} & 01111111 \\ +{} & 1 \\ \hline & 10000000 \end{array} $$

Result: 10000000. We are back to -128!

This means that, computationally, the opposite of -128 is itself. This behavior causes silent bugs in programming.

Be Careful in Programming

In C and C++, computing the absolute value abs() or negating -x of the smallest possible integer is undefined behavior: the language standard does not say what happens, and the compiler may optimize the code assuming this situation never occurs.

Java has no undefined behavior, but that does not save it from the problem: Math.abs(Integer.MIN_VALUE) is specified and returns Integer.MIN_VALUE itself, a negative value coming out of an absolute value function. The bug is the same in both cases; the difference is that in one it is unpredictable and in the other it is documented.

Here we talk about 8 bits, but the same holds for 16, 32, or 64 bits. There will always be a negative number with no positive opposite.

Overflow (Capacity Overflow)
#

In the examples above, we discarded the last carry and the result was right. But that is not always the case.

Overflow occurs when the result of an operation is too large (positively or negatively) to fit in the available number of bits.

Remember: in an 8-bit signed system, we can represent numbers from -128 to +127. If you try to add \(100 + 30\), the result (130) does not fit, and it will invade the sign bit, looking like a negative number.

Overflow detection follows two rules. The “human” (logical) rule and the “technical” (hardware) rule.

  1. Human Rule: If we add two Positive numbers and the result is Negative, there was Overflow. (The same holds for Negative + Negative = Positive).
  2. Technical Rule: Overflow occurs if the carry going into the sign bit is different from the carry coming out of it.
    • There is a carry propagated into the sign bit with no carry coming out of it.
    • There is a carry propagated out of the sign bit without it having received a carry.

Put another way: in two’s complement, overflow occurs if the carry-in (the carry that enters the sign bit) is different from the carry-out (the carry that leaves the sign bit).

Let us see this happening in practice with 8-bit numbers:

Overflow - Positive + Positive becomes Negative

Let us add \(88 + 46\). The mathematical result would be 134. But in 8 signed bits, the maximum is 127. It will go wrong.

Look at the Sign Bit (the leftmost one, bit 7).

$$ \begin{array}{c c c c c c c c c} {}&\scriptscriptstyle 1 & \scriptscriptstyle 1 & \scriptscriptstyle 1 & \scriptscriptstyle 1 & & & & & \\ {} & 0 & 1 & 0 & 1 & 1 & 0 & 0 & 0 \\ {} + & 0 & 0 & 1 & 0 & 1 & 1 & 1 & 0 \\ \hline {} & \textcolor{red}{1} & 0 & 0 & 0 & 0 & 1 & 1 & 0 \end{array} $$

Here the highlighted \(\textcolor{red}{1}\) is the sign bit of the result: it is within the 8 bits, in the column of bit 7.

Analysis:

  1. Look at the sign bit (first column). A “carry” came in (it received 1 from the previous positions).
  2. But no “carry” went out of it, beyond the computation.
  3. It came in and did not go out? Overflow!

The binary result 10000110 starts with 1, indicating a negative number (-122). This is obviously an error, since we added two positives.

Now let us see the opposite case, with two negative numbers overflowing the lower limit (-128).

Overflow - Negative + Negative becomes Positive

Let us do \( -76 + (-68) \). The mathematical result would be -144. The lower limit is -128. It will go wrong.

We represent them in two’s complement and add:

$$ \begin{array}{c c c c c c c c c} \scriptscriptstyle 1 & & \scriptscriptstyle 1 & \scriptscriptstyle 1 & \scriptscriptstyle 1 & \scriptscriptstyle 1 & & & \\ {} & 1 & 0 & 1 & 1 & 0 & 1 & 0 & 0 \\ {} + & 1 & 0 & 1 & 1 & 1 & 1 & 0 & 0 \\ \hline \textcolor{red}{1} & 0 & 1 & 1 & 1 & 0 & 0 & 0 & 0 \end{array} $$

Careful: the highlighted \(\textcolor{red}{1}\) in this example is not the sign bit. It is the carry that came out of the computation, in an extra column to the left of the 8 bits, and it will be discarded. The sign bit of the result is the 0 right next to it.

Analysis:

  1. Look at the sign bit. It added \(1 + 1\), generating 0 and propagating a “carry” out (the red \(\textcolor{red}{1}\)).
  2. But it received no “carry” from the previous column (see the empty space above the second column).
  3. It went out and did not come in? Overflow!

The result 01110000 starts with 0, indicating a positive number (+112). Error detected.

When Overflow Leaves the Paper
#

Overflow is behind some of the most expensive failures ever recorded in software.

Ariane 5, flight 501

On June 4, 1996, the Ariane 5 rocket exploded 39 seconds after liftoff, on its maiden flight. The cause: the inertial reference system converted the horizontal velocity from a 64-bit floating-point number to a 16-bit signed integer. On the Ariane 4, that value had always fit. The Ariane 5 was faster, the number did not fit, and the conversion overflowed.

The software handled the error by shutting down the inertial reference system (IRS) itself, and the backup IRS, which ran exactly the same code, had shut down for the same reason about 72 milliseconds earlier. With no attitude reference, the rocket turned, broke apart under aerodynamic pressure, and triggered its self-destruct. The loss is estimated at US$370 million, caused by a piece of code that did not even need to be active at that point of the flight.

Not every case is tragic. Two well-known examples of the same phenomenon:

  • Pac-Man, level 256. The level counter had 8 bits, that is, it went from 0 to 255. On reaching level 256, the computation that draws the screen overflowed the counter and half of the maze turned into graphical garbage. It became known as the kill screen: the level is impossible to pass, and for decades it was the absolute ceiling of the game.
  • The YouTube counter, in 2014. The view count was stored in a 32-bit signed integer, whose limit is \(2^{31} - 1 = 2{,}147{,}483{,}647\). When the video of Gangnam Style passed that mark, Google migrated the field to 64 bits, and made an official joke about it. With 64 bits, the new ceiling is over 9 quintillion.

In all three cases the mechanism is the same one you just saw in the 8-bit computations above. What changes is the number of bits and, with it, the size of the damage.

Bonus Tip: Spotting Negatives in Hexadecimal
#

In practice, programmers rarely look at sequences of zeros and ones. The most common representation in debuggers and memory editors is Hexadecimal.

How can you tell whether a Hex number is negative (in two’s complement) at a glance?

The rule is universal, no matter whether the number has 8, 16, 32, or 64 bits: just look at the first digit on the left. If that digit represents a binary value that starts with 1 (that is, \(\ge 8\)), the whole integer is negative.

The “Negative Digits” in Hexadecimal are: 8, 9, A, B, C, D, E, F

Examples:

  • 8 bits: 0xFF \(\to\) Starts with F. Negative (-1).
  • 16 bits: 0x8000 \(\to\) Starts with 8. Negative (-32,768).
  • 32 bits: 0xC0000005 \(\to\) Starts with C. Negative.
  • Any size: 0x7FFF... \(\to\) Starts with 7. Positive.

If you are debugging code and see a (signed) integer variable starting with F, know immediately that it is negative. That is all: the first digit tells the sign, not the value. 0xF0000000 also starts with F and is worth \(-268{,}435{,}456\), which is quite different from \(-1\).

There is, however, one unmistakable special case: when all the digits are F, as in 0xFFFFFFFF (32 bits), then the value is indeed -1.

It may seem strange that the visually “largest” number (all filled with F or 1) represents -1. But we can prove it by applying the “Flip and Add 1” rule we just learned.

If we want to know the value of 0xFFFFFFFF (32 bits), we take the reverse path:

  1. Convert to Binary: A sequence of thirty-two 1s (1111...1111).
  2. Flip the bits: All the 1s become 0s (0000...0000).
  3. Add 1: $$ 0 + 1 = 1 $$

Since the original sign bit was 1 (negative) and the magnitude found was 1, the final value is -1.

Therefore, no matter the size of the variable (8, 16, 32, or 64 bits): if all the bits are 1 (or all the hexadecimal digits are F), the number will always be -1 in Two’s Complement.

In Practice: Visualizing in Python
#

If you try to check what we learned here using the Python console, you will get a surprise. Python handles integers differently from languages like C or Java: it has arbitrary precision. This means numbers can have as many bits as needed, with no fixed limit (such as 8 or 32 bits).

Because of this, the built-in bin() function does not show Two’s Complement by default. It just puts a minus sign in front:

x = -5

print(bin(x))   # -0b101
# This is not Two's Complement! It is just "negative 5".

To see how the bits are really stored (like 1111...1011), we need to force Python to limit the number to a given number of bits, using a Bitmask with the AND operator (&).

If we want to see the 8-bit representation, we use the mask 0xFF (which is 11111111):

x = -5

print(bin(x & 0xFF))   # 0b11111011
# Now we are talking! See the bits turned on indicating the negative.

If we want to see it in 32 bits, we use 0xFFFFFFFF:

x = -5

print(bin(x & 0xFFFFFFFF))
# 0b11111111111111111111111111111011

This happens because the & operator treats the number as a pure sequence of bits, revealing the Two’s Complement structure that lies “under the hood”.

Summary Table
#

Concept Rule Example from the article
Radix complement in base \(b\) \(b^d - N\), in a system of \(d\) digits \(10^3 - 734 = 266\)
Two’s complement \(2^d - N\), in a system of \(d\) bits \(2^4 - 5 = 1011_2\)
Arithmetic shortcut flip all the bits and add 1 0101 \(\to\) 1010 \(\to\) 1011
Visual shortcut copy from the right up to the first 1, flip the rest 011100 \(\to\) 100100
Reading a negative subtract \(2^d\) from the unsigned value \(251 - 256 = -5\)
Reading a negative (2nd way) give the MSB weight \(-2^{d-1}\) \(-128 + 64 + 32 + 16 + 8 + 2 + 1 = -5\)
Subtracting by adding \(A - B = A + \text{comp}_2(B)\), discarding the final carry \(25 - 19 = 6\)
Unsigned range \(0\) to \(2^N - 1\) 8 bits: 0 to 255
Signed range \(-2^{N-1}\) to \(2^{N-1} - 1\) 8 bits: \(-128\) to \(+127\)
Why the range is asymmetric there is only one zero, so one negative combination is left over \(-128\) has no opposite
Overflow (human rule) positive + positive giving negative, or the reverse \(88 + 46 = -122\)
Overflow (technical rule) the carry into the sign bit differs from the one out of it \(-76 + (-68) = +112\)
Negative in hexadecimal first digit \(\ge 8\) 0xF0000000 is negative
The case of \(-1\) all digits equal to F 0xFFFFFFFF is worth \(-1\)
In Python integers have arbitrary precision; use a mask to see the bits bin(-5 & 0xFF) \(\to\) '0b11111011'

Conclusion
#

The Two’s Complement system is an elegant solution: thanks to it, processors can perform additions and subtractions using the same logic circuit (the adder), just flipping bits when necessary. This simplifies chip design and reduces costs.

With that, we close the cycle of integers. We know how to represent them, convert them, and operate on them, including the negatives.

But what happens when we need precision beyond the integer? How does the computer deal with \(0.5\) or \(3.14\)? And why does the & operator work as shown above?

These questions will be answered in the next articles of the series From Zero to Float. Stay tuned!

From Zero to Float - This article is part of a series.
Part 3: This Article

Related