Related
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?
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:
The inputs both have sign bits that are off, while the result has a sign bit that is on.
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:
The carry flag is set during addition if there is a carry out of the most significant bit (the sign bit).
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.)
Ladies and gentlemen, I have successfully been able to understand adding, etc. for unsigned binary numbers. However, this Two's complement has me beat.. Let me explain with some examples.
Example practice problem, perform each arithmetic using 8-bit signed integer storage system, under two's complement:
1111111
01100001
+ 00111111
----------
10100000 <== My Answer (idk if right / wrong), but i think right.
So it doesn't exceed 8 bits, but we have changed (positive + positive) = negative. That has to be an overflow, because the sign is changing, right? (I never understood what the carry in MSB and carry out MSB's were).
The reallllly tricky part for me are the following equations: (negative + negative) which in reality is equal to (negative - positive).
111111
10111111
+ 10010101
----------
1 | 01010100
So I think this should be wrong because when we discard the overflow bit (the 1 that is way out in left field), it turns the 8-bit representation into a positive number, when it should be a negative. SO this would entail an overflow, no?
The following equation is similar:
1111
10001110
+ 10110101
----------
1 | 01000011
Understandably, if we were working with 16-bit, etc. Then these wouldn't be overflows, because the signs aren't changing, the math is correct. But since when we store the 8-bit representation of these numbers, we lose the MSB, that would flip the signs.
But one thing that I noticed about my theory is, whenever adding two negative numbers, the MSB's will obviously always be 1, therefore you will always have a carry, which means you would always have an overflow.
** I think the more logical conclusion is that I am forgetting to convert the second negative to a positive or something prior to adding them, or something along those lines. But I've tried youtube and various research online. And TBH, my professor is terrible with the whole "communication" thing.. I would appreciate any help the community can give, so I can push past these problems and onto harder material XD.
Yes, if you discard a 1 carry bit then that signals overflow. Don't worry about overflow. It is correct to discard the carry bit.
But since when we store the 8-bit representation of these numbers, we lose the MSB, that would flip the signs.
It's important not to think of discarding a 1 bit as flipping the sign. First, discarding a carry bit is not the same as flipping the sign bit. You can discard the carry bit from a negative result and still end up with a negative answer. For instance:
1111111
11111111 (-1)
+ 11111111 (-1)
--------
1 | 11111110 (-2)
The final 1 carry bit is discarded, but the answer's sign doesn't flip.
Second, even if you're thinking of flipping (as opposed to discarding) the sign bit, it's not good to think of that as flipping the sign. In sign magnitude representation flipping the sign bit will flip the sign of the number. But in one's and two's complement, negation is more than just flipping the leftmost bit. If you just flip the bit you get a very different number. Yes, it has the opposite sign, but it's not the same number.
Sign Mag. | One's Compl. | Two's Compl.
01111111 = 127 | 127 | 127
11111111 = -127 | -0 | -1
Yes, your math is correct.
The elegance of two's compliment is that addition "just works" without any special considerations for the sign bit. The reason both of those subtractions underflow is because the magnitudes of the numbers are already quite large.
Let's do the last two questions in decimal:
(-65)
+ (-107)
--------
(-172) which underflows to 84.
(-114)
+ (-75)
--------
(-189) which underflows to 67.
The lowest signed 8-bit value is -128, so both of them underflow.
I am trying to times 52 by 1000 and i am getting a negative result
int getNewSum = 52 * 1000;
but the following code is ouputting a negative result: -13536
An explanation of how two's complement representation works is probably better given on Wikipedia and other places than here. What I'll do here is to take you through the workings of your exact example.
The int type on your Arduino is represented using sixteen bits, in a two's complement representation (bear in mind that other Arduinos use 32 bits for it, but yours is using 16.) This means that both positive and negative numbers can be stored in those 16 bits, and if the leftmost bit is set, the number is considered negative.
What's happening is that you're overflowing the number of bits you can use to store a positive number, and (accidentally, as far as you're concerned) setting the sign bit, thus indicating that the number is negative.
In 16 bits on your Arduino, decimal 52 would be represented in binary as:
0000 0000 0011 0100
(2^5 + 2^4 + 2^2 = 52)
However, the result of multiplying 52 by 1,000 -- 52,000 -- will end up overflowing the magnitude bits of an int, putting a '1' in the sign bit on the end:
*----This is the sign bit. It's now 1, so the number is considered negative.
1100 1011 0010 0000
(typically, computer integer arithmetic and associated programming languages don't protect you against doing things like this, for a variety of reasons, mostly related to efficiency, and mostly now historical.)
Because that sign bit on the left-hand end is set, to convert that number back into decimal from its assumed two's complement representation, we assume it's a negative number, and then first take the one's complement (flipping all the bits):
0011 0100 1101 1111
-- which represents 13,535 -- and add one to it, yielding 13,536, and call it negative: -13,536, the value you're seeing.
If you read up on two's complement/integer representations in general, you'll get the hang of it.
In the meantime, this probably means you should be looking for a bigger type to store your number. Arduino has unsigned integers, and a long type, which will use four bytes to store your numbers, giving you a range from -2,147,483,648 to 2,147,483,647. If that's enough for you, you should probably just switch to use long instead of int.
Matt's answer is already a very good in depth explanation of what's happening, but for those looking for a more TL;dr practical answer:
Problem:
This happens quite often for Arduino programmers when they try to assign (= equal sign) the result of an arithmetic (usually multiplication) to a normal integer (int). As mentioned, when the result is bigger than the memory size compiler has assigned to the variables, overflowing happens.
Solution 1:
The easiest solution is to replace the int type with a bigger datatype considering your needs. As this tutorialspoint.com tutorial has explained there are different integer types we can use:
int:
16 bit: from -32,768 to 32,767
32 bit: from -2,147,483,648 to 2,147,483,647
unsigned int: from 0 to 65,535
long: from 2,147,483,648 to 2,147,483,647
unsigned long: from 0 to 4,294,967,295
Solution 2:
This works only if you have some divisions with big enough denominators, in your arithmetic. In Arduino compiler multiplication is calculated prior to the division. so if you have some divisions in your equation try to encapsulate them with parentheses. for example if you have a * b / c replace it with a * (b / c).
I'm reading the textbook Computer Organization And Design by Hennessey and Patterson (4th edition). On page 225 they describe how overflow is detected in signed, 2's complement arithmetic. I just can't even understand what they're talking about.
"How do we detect [overflow] when it does occur? Clearly, adding or
substracting two 32-bit numbers can yield a result that needs 33 bits
to be fully expressed."
Sure. And it won't need 34 bits because even the smallest 34 bit number is twice the smallest 33 bit number, and we're adding 32 bit numbers.
"The lack of a 33rd bit means that when overflow occurs, the sign bit
is set with the value of the result instead of the proper sign of
the result."
What does this mean? The sign bit is set with the "value" of the result... meaning it's set as if the result were unsigned? And if so, how does that follow from the lack of a 33rd bit?
"Since we need just one extra bit, only the sign bit can be wrong."
And that's where they lost me completely.
What I'm getting from this is that, when adding signed numbers, there's an overflow if and only if the sign bit is wrong. So if you add two positives and get a negative, or if you add two negatives and get a positive. But I don't understand their explanation.
Also, this only applies to unsigned numbers, right? If you're adding signed numbers, surely detecting overflow is much simpler. If the last half-adder of the ALU sets its carry bit, there's an overflow.
note: I really don't know what tags are appropriate here, feel free to edit them.
Any time you want to deal with these kind of ALU items be it add, subtract, multiply, etc, start with 2 or 3 bit numbers, much easier to get a handle on than 32 or 64 bit numbers. After 2 or 3 bits it doesn't matter if it is 22 or 2200 bits it all works exactly the same from there on out. Basically you can by hand if you want make a table of all 3 bit operands and their results such that you can examine the whole table visually, but a table of all 32 bit operands against all 32 bit operands and their results, can't do that by hand in a reasonable time and cannot examine the whole table visually.
Now twos complement, that is just a scheme for representing positive and negative numbers, and it is not some arbitrary thing it has a reason, the reason for the madness is that your adder logic (which is also what the subtractor uses which is the same kind of thing the multiplier uses) DOES NOT CARE ABOUT UNSIGNED OR SIGNED. It does not know the difference. YOU the programmer cares in my three bit world the bit pattern 0b111 could be a positive seven (+7) or it could be a negative one. Same bit pattern, feed it to the add logic and the same thing comes out, and the answer that comes out I can choose to interpret as unsigned or twos complement (so long as I interpret the operands and the result all as either unsigned or all as twos complement). Twos complement also has the feature that for negative numbers the most significant bit (msbit) is set, for positive numbers it is zero. So it is not sign plus magnitude but we still talk about the msbit being the sign bit, because except for two special numbers that is what it is telling us, the sign of the number, the other bits are actually telling us the magnitude they are just not an unsigned magnitude as you might have in sign+magnitude notation.
So, the key to this whole question is understanding your limits. For a 3 bit unsigned number our range is 0 to 7, 0b000 to 0b111. for a 3 bit signed (twos complement) interpretation our range is -4 to +3 (0b100 to 0b011). For now limiting ourselves to 3 bits if you add 7+1, 0b111 + 0b001 = 0b1000 but we only have a 3 bit system so that is 0b000, 7+1 = 8, we cannot represent 8 in our system so that is an overflow, because we happen to be interpreting the bits as unsigned we look at the "unsigned overflow" which is also known as the carry bit or flag. Now if we take those same bits but interpret them as signed, then 0b111 (-1) + 0b001 (+1) = 0b000 (0). Minus one plus one is zero. No overflow, the "signed overflow" is not set...What is the signed overflow?
First what is the "unsigned overflow".
The reason why "it all works the same" no matter how many bits we have in our registers is no different than elementary school math with base 10 (decimal) numbers. If you add 9 + 1 which are both in the ones column you say 9 + 1 = zero carry the 1. you carry a one over to the tens column then 1 plus 0 plus 0 (you filled in two zeros in the tens column) is 1 carry the zero. You have a 1 in the tens column and a zero in the ones column:
1
09
+01
====
10
What if we declared that we were limited to only numbers in the ones column, there isn't any room for a tens column. Well that carry bit being a non-zero means we have an overflow, to properly compute the result we need another column, same with binary:
111
111
+ 001
=======
1000
7 + 1 = 8, but we cant do 8 if we declare a 3 bit system, we can do 7 + 1 = 0 with the carry bit set. Here is where the beauty of twos complement comes in:
111
111
+ 001
=======
000
if you look at the above three bit addition, you cannot tell by looking if that is 7 + 1 = 0 with the carry bit set or if that is -1 + 1 = 0.
So for unsigned addition, as we have known since grade school that a carry over into the next column of something other than zero means we have overflowed that many placeholders and need one more placeholder, one more column, to hold the actual answer.
Signed overflow. The sort of academic answer is if the carry in of the msbit column does not match the carry out. Let's take some examples in our 3 bit world. So with twos complement we are limited to -4 to +3. So if we add -2 + -3 = -5 that wont work correct?
To figure out what minus two is we do an invert and add one 0b010, inverted 0b101, add one 0b110. Minus three is 0b011 -> 0b100 -> 0b101
So now we can do this:
abc
100
110
+ 101
======
011
If you look at the number under the b that is the "carry in" to the msbit column, the number under the a the 1, is the carry out, these two do not match so we know there is a "signed overflow".
Let's try 2 + 2 = 4:
abc
010
010
+ 010
======
100
You may say but that looks right, sure unsigned it does, but we are doing signed math here, so the result is actually a -4 not a positive 4. 2 + 2 != -4. The carry in which is under the b is a 1, the carry out of the msbit is a zero, the carry in and the carry out don't match. Signed overflow.
There is a shortcut to figuring out the signed overflow without having to look at the carry in (or carry out). if ( msbit(opa) == msbit(opb) ) && ( msbit(res) != msbit(opb) ) signed overflow, else no signed overflow. opa being one operand, opb being the other and res the result.
010
+ 010
======
100
Take this +2 + +2 = -4. msbit(opa) and msbit(opb) are equal, and the result msbit is not equal to opb msbit so this is a signed overflow. You could think about it using this table:
x ab cr
0 00 00
0 01 01
0 10 01
0 11 10 signed overflow
1 00 01 signed overflow
1 01 10
1 10 10
1 11 11
This table is all the possible combinations if carry in bit, operand a, operand b, carry out and result bit for a single column turn your head sideways to the left to sort of see this x is the carry in, a and b columns are the two operands. cr as a pair is the result xab of 011 means 0+1+1 = 2 decimal which is 0b10 binary. So taking the rule that has been dictated to us, that if the carry in and carry out do not match that is a signed overflow. Well the two cases where the item in the x column does not match the item in the c column are indicated those are the cases where a and b inputs match each other, but the result bit is the opposite of a and b. So assuming the rule is correct this quick shortcut that does not require knowing what the carry bits are, will tell you if there was a signed overflow.
Now you are reading an H&P book. Which probably means mips or dlx, neither mips or dlx deal with carry and signed flags in the way that most other processors do. mips is not the best first instruction set IMO primarily for that reason, their approach is not wrong in any way, but being the oddball, you will spend forever thinking differently and having to translate when going to most other processors. Where if you learned the typical znvc flags (zero flag, negative flag, v=signed overflow, c=carry or unsigned overflow) way then you only have to translate when going to mips. Normally these are computed on every alu operation (for the non-mips type processors) you will see signed and unsigned overflow being computed for add and subtract. (I am used to an older mips, maybe this gen of books and the current instruction set has something different). Calling it addu add unsigned right at the start of mips after learning all of the above about how an adder circuit does not care about signed vs unsigned, is a huge problem with mips it really puts you in the wrong mindset for understanding something this simple. Leads to the belief that there is a difference between signed addition and unsigned addition when there isn't. It is only the overflow flags that are computed differently. Now multiply, and divide there is definitely a twos complement vs unsigned difference and you ideally need a signed multiply and an unsigned multiply or you need to deal with the limitation.
I recommend a simple (depending on how strong your bit manipulation is and twos complement) exercise that you can write in some high level language. Basically take all the combinations of unsigned numbers 0 to 7 added to 0 to 7 and save the result. Print out both as decimal and as binary (three bits for operands, four bits for result) and if the result is greater than 7 print overflow as well. Repeat this using signed variables using the numbers -4 to +3 added to -4 to +3. print both decimal with a +/- sign and the binary. If the result is less than -4 or greater than +3 print overflow. From those two tables you should be able to see that the rules above are true. Looking strictly at the operand and result bit patterns for the size allowed (three bits in this case) you will see that the addition operation gives the same result, same bit pattern for a given pair of inputs independent of whether those bit patterns are considered unsigned or twos complement. Also you can verify that unsigned overflow is when the result needs to use that fourth column, there is a carry out off of the msbit. For signed when the carry in doesn't match the carry out, which you see using the shortcut looking at the msbits of the operands and result. Even better is to have your program do those comparisons and print out something. So if you see a note in your table that the result is greater than 7 and a note in your table that bit 3 is set in the result, then you will see for the unsigned table that is always the case (limited to inputs of 0 to 7). And the more complicated one, signed overflow, is always when the result is less than -4 and greater than 3 and when the operand upper bits match and the result upper bit does not match the operands.
I know this is super long and very elementary. If I totally missed the mark here, please comment and I will remove or re-write this answer.
The other half of the twos complement magic. Hardware does not have subtract logic. One way to "convert" to twos complement is to "invert and add one". If I wanted to subtract 3 - 2 using twos complement what actually happens is that is the same as +3 + (-2) right, and to get from +2 to to -2 we invert and add one. Looking at our elementary school addition, did you notice the hole in the carry in on the first column?
111H
111
+ 001
=======
1000
I put an H above where the hole is. Well that carry in bit is added to the operands right? Our addition logic is not a two input adder it is a three input adder yes? Most of the columns have to add three one bit numbers in order to compute two operands. If we use a three input adder on the first column now we have a place to ... add one. If I wanted to subtract 3 - 2 = 3 + (-2) = 3 + (~2) + 1 which is:
1
011
+ 101
=====
Before we start and filled in it is:
1111
011
+ 101
=====
001
3 - 2 = 1.
What the logic does is:
if add then carry in = 0; the b operand is not inverted, the carry out is not inverted.
if subtract then carry in = 1; the b operand is inverted, the carry out MIGHT BE inverted.
The addition above shows a carry out, I didn't mention that this was an unsigned operation 3 - 2 = 1. I used some twos complement tricks to perform an unsigned operation, because here again no matter whether I interpret the operands as signed or unsigned the same rules apply for if add or if subtract. Why I said that the carry out MIGHT BE inverted is that some processors invert the carry out and some don't. It has to do with cascading operations, taking say a 32 bit addition logic and using the carry flag and an add with carry or subtract with borrow instruction creating a 64 bit add or subtract, or any multiple of the base register size. Say you have two 64 bit numbers in a 32 bit system a:b + c:d where a:b is the 64 bit number but it is held in the two registers a and b where a is the upper half and b is the lower half. so a:b + c:d = e:f on a 32 bit system unsigned that has a carry bit and add with carry:
add f,b,d
addc e,a,c
The add leaves its carry out bit from the upper most bit lane in the carry flag in the status register, the addc instruction is add with carry takes the operands a+c and if the carry bit is set then adds one more. a+c+1 putting the result in e and the carry out in the carry flag, so:
add f,b,d
addc e,a,c
addc x,y,z
Is a 96 bit addition, and so on. Here again something very foreign to mips since it doesn't use flags like other processors. Where the invert or don't invert comes in for signed carry out is on the subtract with borrow for a particular processor. For subtract:
if subtract then carry in = 1; the b operand is inverted, the carry out MIGHT BE inverted.
For subtract with borrow you have to say if the carry flag from the status register indicates a borrow then the carry in is a 0 else the carry in is a 1, and you have to get the carry out into the status register to indicate the borrow.
Basically for the normal subtract some processors invert b operand and carry on in the way in and carry out on the way out, some processors invert the b operand and carry in in the way in but don't invert carry out on the way out. Then when you want to do a conditional branch you need to know if the carry flag means greater than or less than (often the syntax will have a branch if greater or branch if less than and sometimes tell you which one is the simplified branch if carry set or branch if carry clear). (If you don't "get" what I just said there that is another equally long answer which won't mean anything so long as you are studying mips).
As a 32-bit signed integers are represented by 1 sign-bit and 31 bits for the actual number we are effectively adding two 31 bit-numbers. Hence the 32nd bit (sign bit) will be where the overflow will be visible.
"The lack of a 33rd bit means that when overflow occurs, the sign bit is set with the value of the result instead of the proper sign of the result."
Imagine the following addition of two positive numbers (16 bit to simpify):
0100 1100 0011 1010 (19514)
+ 0110 0010 0001 0010 (25106)
= 1010 1110 0110 1100 (-20884 [or 44652])
For the summation of two large negative numbers however the extra bit would be required
1100 1100 0011 1010
+ 1110 0010 0001 0010
=11010 1110 0110 1100
Usually the CPU have this 33rd bit (or whatever bitsize it operates on +1) exposed as a overflow-bit in the micro-architecture.
Their description relates to operations on values with a particular bit sequence: the first bit corresponds to the sign of the value, and the other bits relate to the magnitude of that value.
What does this mean? The sign bit is set with the "value" of the result...
They mean that the overflow bit - the one that is a consequence of adding two numbers that need to spill into the next digit over - is dumped into the same place that the sign bit should be.
"Since we need just one extra bit, only the sign bit can be wrong."
All this means is that, when you perform arithmetic that overflows, the only bit whose value may be incorrect is the sign bit. All of the other bits are still the value they should be.
This is a consequence of what was described above: confusion between the sign bit's value due to overflow.
I have some basic doubts, but every time I sit to try my hands at interview questions, these questions and my doubts pop up.
Say A = 5, B = -2. Assuming that A and B are 4-bytes, how does the CPU do the A + B addition?
I understand that A will have sign bit (MSB) as 0 to signify a positive value
and B will have sign bit as 1 to signify a negative integer.
Now when in C++ program, I want to print A + B, does the addition module of the ALU (Arithmetic Logic Unit) first check for sign bit and then decide to do subtraction and then follow the procedure of subtraction. How subtraction is done will be my next question.
A = 5
B = 2
I want to do A - B. The computer will take 2's complement of B and add A + 2's complement of B and return this (after discarding the extra bit on left)?
A = 2
B = 5
to do A - B. How does the computer do in this case?
I understand that any if-then etc kind of conditional logic all will be done in hardware inside ALU. computing 2s complement etc,discarding extra bit all will be done in hardware inside ALU. How does this component of ALU look like?
The whole reason we use 2's-complement is that addition is the same whether the numbers are positive or negative - there are no special cases to consider, like there are with 1's-complement or signed-magnitude representations.
So to find A-B, we can just negate B and add; that is, we find A + (-B), and because we're using 2's-complement, we don't worry if (-B) is positive or negative, because the addition-algorithm works the same either way.
Think in terms of two or three bits and then understand that these things scale up to 32 or 64 or howevermany bits.
First, lets start with decimal
99
+22
===
In order to do this we are going to have some "Carry the one's" going on.
11
99
+22
===
121
9 plus 2 is 1 carry the one, 1 plus 9 plus 2 is 2 carry the one...
The point being though to notice that to add two numbers I actually needed three rows, for at least some of it I might need to be able to add three numbers. Same thing with an adder in an alu, each column or bit lane, single bit adder, needs to be able to add two inputs plus a carry in bit, and the output is a one bit result and a one bit carry.
Since you used 5 and 2 lets do some 4 bit binary math
0101
+0010
=====
0111
We didnt need a carry on this one, but you can see the math worked, 5 + 2 = 7.
And if we want to add 5 and -2
11
0101
+1110
=====
0011
And the answer is 3 as expected, not really surprising but we had a carry out. And since this was an add with a minus number in twos complement it all worked, there was no if sign bit then, twos complement makes it so we dont care just feed the adder the two operands.
Now if you want to make a subtle difference, what if you want to subtract 2 from 5, you select the subtract instruction not add. Well we all learned that negating in twos complement means invert and add one. And we saw above that a two input adder really needs a third input for carry in so that it can be cascaded to however wide the adder needs to be. So instead of doing two add operations, invert and add 1 being the first add the real add all we have to do is invert and set the carry in:
Understand that there is no subtract logic, it adds the negative of whatever you feed it.
v this bit is normally zero, for a subtract we set this carry in bit
11 11
0101 five
+1101 ones complement of 2
=====
0011
And what do you know we get the same answer...It doesnt matter what the actual values are for either of the operands. if it is an add operation you put a zero on the carry in bit and feed it to the adder. If it is a subtract operation you invert the second operand and put a one on the carry in and feed it to the same adder. Whatever falls out falls out. If your logic has enough bits to hold the result then it all works, if you do not have enough room then you overflow.
There are two kinds of overflow, unsigned, and signed. Unsigned is simple it is the carry bit. Signed overflow has to do with comparing the carry in bit on the msbit column with the carry out bit for that column. For our math above you see that the carry and carry out of that msbit column is the same, both are a one. And we happen to know by inspection that a 4 bit system has enough room to properly represent the numbers +5, -2, and +3. A 4 bit system can represent the numbers +7 down to -8. So if you were to add 5 and 5 or -6 and -3 you would get a signed overflow.
01 1
0101
+0101
=====
1010
Understand that the SAME addition logic is used for signed and unsigned math, it is up to your code not the logic to virtually define if those bits were considered twos complement signed or unsigned.
With the 5 + 5 case above you see that the carry in on the msbit column is a 1, but the carry out is a 0 that means the V flag, the signed overflow flag, will be set by the logic. At the same time the carry out of that bit which is the C flag the carry flag, will not be set. When thinking unsigned 4 bits can hold the numbers 0 to 15 so 5 + 5 = 10 does not overflow. But when thinking signed 4 bits can hold +7 to -8 and 5 + 5 = 10 is a signed overflow so the V flag is set.
if/when you have an add with carry instruction they take the SAME adder circuit and instead of feeding the carry in a zero it is fed the carry flag. Likewise a subtract with borrow, instead of feeding the carry in a 1 the carry in is either a 1 or 0 based on the state of the carry flag in the status register.
Multiplication is whole other story, binary makes multiplication much easier than when done with decimal math, but you DO have to have different unsigned and signed multiplication instructions. And division is its own separate beast, which is why most instruction sets do not have a divide. Many do not have a multiply because of the number of gates or clocks it burns.
You are a bit wrong in the sign bit part. It's not just a sign bit - every negative number is converted to 2's complement. If you write:
B = -2
The compiler when compiling it to binary will make it:
1111 1111 1111 1111 1111 1111 1111 1110
Now when it wants to add 5, the ALU gets 2 numbers and adds them, a simple addition.
When the ALU gets a command to subtract it is given 2 numbers - it makes a NOT to every bit of the second number and makes a simple addition and adds 1 more (because 2's complement is NOT to every bit +1).
The basic thing here to remember is that 2's complement was selected for exactly the purpose of not having to make 2 separate procedures for 2+3 and for 2+(-3).
does the addition module of ALU (Arithmetic Logic Unit) first check for sign bit and then decide to do subtraction and then follow the procedure of subtraction
No, in one's and two's complement there's no differentiation between adding/subtracting a positive or negative number. The ALU works the same for any combination of positive and negative values
So the ALU is basically doing A + (-B) for A - B, but it doesn't need a separate negation step. Designers use a clever trick to make adders do both add and sub in the same cycle length by adding only a muxer and a NOT gate along with the new input Binvert in order to conditionally invert the second input. Here's a simple ALU example which can do AND/OR/ADD/SUB
Computer Architecture - Full Adder
The real adder is just a box with the plus sign inside ⊞ which adds a with b or ~b and carry in, producing the sum and carry out. It works by realizing that in two's complement -b = ~b + 1, so a - b = a + ~b + 1. That means we just need to set the carry in to 1 (or negate the carry in for borrow in) and invert the second input (i.e. b). This type of ALU can be found in various computer architecture books like
Digital Design and Computer Architecture
Computer Organization and Design MIPS Edition: The Hardware/Software Interface
Computer Organization and Design RISC-V Edition: The Hardware Software Interface
In one's complement -b = ~b so you don't set the carry in when you want to subtract, otherwise the design is the same. However two's complement has another advantage: operations on signed and unsigned values also work the same, so you don't even need to distinguish between signed and unsigned types. For one's complement you'll need to add the carry bit back to the least significant bit if the type is signed
With some simple simple modification to the above ALU they can now do 6 different operations: ADD, SUB, SLT, AND, OR, NOR
CSE 675.02: Introduction to Computer Architecture
Multiple-bit operations are done by concatenating multiple single-bit ALUs above. In reality ALUs are able to do a lot more operations but they're made to save space with the similar principle
In 2's-complement notation: not B = -B -1 or -B = (not B) + 1. It can be checked on a computer or on paper.
So A - B = A + (not B) + 1 which can be performed with:
1 bitwise not
1 increment
1 addition
There's a trick to inefficiently increment and decrement using just nots and negations.
For example if you start with the number 0 in a register and perform:
not, neg, not, neg, not, neg, ... the register will have values:
-1, 1, -2, 2, -3, 3, ...
Or as another 2 formulas:
not(-A) = A - 1
-(not A) = A + 1