Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does a processor without an overflow flag perform signed arithmetic?

I know that addition of two unsigned integers larger than the bus size of a given processor can be achieved via the carry flag. And normally, the same is true for signed integers using the overflow flag. However, the Intel 8085 only possesses a Sign flag, not an Overflow flag, so how does it deal with signed integer arithmetic?

like image 1000
parallel_resistance Avatar asked Mar 09 '23 13:03

parallel_resistance


1 Answers

As you know, the overflow flag is only relevant for signed integer arithmetic. On processors whose ALU has both overflow and carry flags (like x86), both of these flags get set according to the result of a binary arithmetic operation, but it's up to the programmer to decide how to interpret them. Signed arithmetic uses the overflow flag; unsigned arithmetic uses the carry flag. Looking at the wrong one gives you meaningless data.

There are two cases where the overflow flag would be turned on during a binary arithmetic operation:

  1. The inputs both have sign bits that are off, while the result has a sign bit that is on.
  2. The inputs both have sign bits that are on, while the result has a sign bit that is off.

Basically, then, the overflow flag gets set when the sign bit of the result does not match the sign bit of the input operands. In all other cases, the overflow flag is turned off.

Taking a couple of examples:

  • 0100 + 0001 = 0101 (overflow flag off)
  • 0100 + 0100 = 1000 (overflow flag on)
  • 0110 + 1001 = 1111 (overflow flag off)
  • 1000 + 1000 = 0000 (overflow flag on)
  • 1000 + 0001 = 1001 (overflow flag off)
  • 1100 + 1100 = 1000 (overflow flag off)

Notice that the state of the overflow flag depends only on the sign bits of the three numbers; thus, you need only look at these bits. This makes intuitive sense. If you add two positive numbers to get a negative, then the answer must be wrong because two positive numbers should give a positive result. Conversely, if you add two negative numbers and get a positive number, that also must be wrong. A positive number added to a negative number can never overflow because the sum lies between the two input values. Thus, arithmetic of mixed-signed values never turns on the overflow flag.

(Obviously this all assumes two's-complement arithmetic.)

Thus, you can easily calculate the state of the overflow flag even if the processor's ALU doesn't do it for you automatically. All you need to do is look at the sign bits of the three values, specifically the binary carry into the sign bit and the binary carry out of the sign bit. Overflow occurs when a bit is carried into the sign-bit place and no corresponding carry out occurs.

These C functions implement the logic:

// For the binary (two's complement) addition of two signed integers,
// an overflow occurs if the inputs have the same sign AND ALSO the
// sign of the result is different from the signs of the inputs.
bool GetOverflowFlagForAddition(int op1, int op2, int result)
{
   return (~(op1 ^ op2) & (op1 ^ result)) < 0;
}

// For the binary (two's complement) subtraction of two signed integers,
// an overflow occurs if the inputs have the same sign AND ALSO the
// sign of the result matches the signs of the inputs.
bool GetOverflowFlagForSubtraction(int op1, int op2, int result)
{
   return ((op1 ^ op2) & (op1 ^ result)) < 0;
}

(There are many different ways that you could write this, of course.)

Or, to put it in the terms that Iwillnotexist Idonotexist did in a comment: "Overflow can be defined as the XOR of the carry into and out of the sign bit." Overflow has occurred if the carry in does not equal the carry out at that particular (leftmost) bit.

A more formal definition is that the overflow flag is the XOR of the carry-out of the high two bits of the result. Symbolically, for an 8-bit value: O = C6 ^ C7, where O means "overflow" and C means "carry". This is just a restatement of the definition I already gave: overflow happens if the carry out is different from the carry into the highest-order bit (in this case, bit 7).

See also Ken Shirriff's article on how the overflow flag works arithmetically (this is in the context of the 6502, another popular 8-bit processor). He also explains the implementation of the overflow flag at the silicon level in the 6502.


Okay, so what does the carry flag mean? The carry flag indicates an overflow condition in unsigned arithmetic. There are again two cases where it is set:

  1. The carry flag is set during addition if there is a carry out of the most significant bit (the sign bit).

  2. The carry flag is set during subtraction if there is a borrow into the most significant bit (the sign bit).

In all other cases, the carry flag is turned off. Again, examples:

  • 1111 + 0001 = 0000 (carry flag on)
  • 0111 + 0001 = 1000 (carry flag off)
  • 0000 - 0001 = 1111 (carry flag on)
  • 1000 - 0001 = 0111 (carry flag off)

Just in case it isn't obvious, it bears explicitly pointing out that subtraction is the same as addition of the two's-complement negation, so the latter two examples can be rewritten in terms of addition as:

  • 0000 + 1111 = 1111 (carry flag on)
  • 1000 + 1111 = 0111 (carry flag off)

…but note that the carry for subtraction is the inverse of the carry set by addition.


Putting all of this together, then, you can absolutely implement the overflow flag in terms of the carry and sign flags. The carry flag gets set if you have a carry out of the most-significant bit (the sign bit). The sign flag gets set if the result has its sign bit set, which means that there was a carry into the most-significant bit. By our definition of the overflow flag above, OF == CF ^ SF, because overflow is the carry coming out of the sign bit XORed with the carry coming into the sign bit. If the carry in does not equal the carry out, then signed overflow occurred.

Interestingly, though, Ken Shirriff's reverse-engineering of the 8085 processor shows that it does, in fact, have an overflow flag—it's just undocumented. This is bit 1 of the 8-bit flag status register. It is known as "V" and, as Ken explains here, is implemented in exactly the way discussed above, by XORing the carry into the most-significant bit with the carry out of the most-significant bit—C6 ^ C7, with these values coming directly from the ALU. (He also describes in the same article how the other undocumented flag, the "K" flag, is implemented in terms of the "V" flag and the sign flag, yielding a flag that is useful in signed comparisons, but a bit beyond the scope of this answer.)

like image 163
Cody Gray Avatar answered Apr 09 '23 00:04

Cody Gray