Using Them for Simple Calculations
Hi! Welcome to the third chapter of this series. Now, we'll try to learn a bit more about doing math in assembly. I hope that you really grasped the idea discussed in the previous chapter. The arithmatic operations discussed here is done in integers. No reals yet.
Doing math in assembly will likely change the flag. To refresh our mind: Flags are just the way to tag something after the processor executes an operation: whether the last operation holds zero value, negatives, has a carry or borrow (for addition and subtractions), and so on. If you'd like to review, please click here.
Addition And Subtraction
Additions and subtractions are straightforward. It takes the following formats:
add x, y ; --> means: x = x + y sub x, y ; --> means: x = x - y
Additions and substractions can be done on any registers except the segment registers. Just like the mov command, you can have one of them as memory locations. I encourage you to experiment on how to use this command. What is allowable and what is not. The following is legal in assembly (assuming the variables are already defined):
add ax, 5 ; --> means: ax = ax + 5 add bx, cx ; --> means: bx = bx + cx add [n], ax ; --> means: [n] = [n] + ax add cx, [n] ; --> means: cx = cx + [n] sub di, di ; --> means: di = di - di (in other words: di = 0) sub ax, si ; --> means: ... sub ax, 4 ; --> means: ...
As I pointed out in the previous chapter, for those of you that has 80286 processor or faster may actually add or subtract variables with constants. But don't forget to add the word ptr or dword ptr as appropriate. For example:
add [word ptr i], 10
Ah... that's simple! However, remember that the operation depends on the registers you are using. For example, if you say add al, cl, you are doing an 8-bit addition. So, the processor actually "expects" (well... sort of) the result to be bounded within 8-bit range (i.e. 0 to 255). Similarly, for 16-bit addition, it should be within 0 to 65535. For substraction, the processor also "expects" it to be non-negative (i.e. no borrows). "Oh? So we have such limitation?" you asked. Well, we actually have a "work around" to do that.
If the result of an addition overflows, the carry flag is set to 1, otherwise it is 0. By detecting the carry flag after doing the addition, we'll know whether the last addition overflows or not. Similarly, if the result of subtraction requires a borrow, then ... (guess what) the carry flag is also set to 1, otherwise it is 0. Wait.... is this a typo? No... The internal circuitry to store carry or borrow is the same. How can this be? Well, this will involve a deeper understanding about computer logic, which is out of scope of this chapter. Don't bother... :-) Just accept it for now.
Now the next question would be: "So, if the last addition overflows, does the next add automatically count the carry flag too?" Good question. The answer is no. However, Intel processor has a special instruction called adc. This command behaves similarly as the add command. The only extra thing is that it also add the value carry flag along. So, this may be very handy to add large integers. Suppose you'd like to add a 32-bit integers with 16-bit registers. How can we do that? Well, let's say that the first integer is held on the register pair DX:AX, and the second one is on BX:CX. This is how:
add ax, cx adc dx, bx
Ah, so first, the lower 16-bit is added by add ax, cx. Then the higher 16-bit is added using adc instead of add. It is because: if there are overflows, the carry bit is automatically added in the higher 16-bit. So, no cumbersome checking. This method can be extended to 64 bits and so on... Note that: If the 32-bit integer addition overflows too at the higher 16-bit, the result will not be correct and the carry flag is set, e.g. Adding 5 billion to 5 billion.
For the subtraction, we have similar instruction called sbb. It is pretty much the counterpart for sub. I won't discuss it much here. You should try it out yourself.
How about adding and subtracting negative values? add, adc, sub, and sbb handles that "automatically". Well, again, the internal logic for addition and subtraction both for positive and negative values are the same. So, don't worry if you have negative operands. These instruction can go smoothly.
Multiplication, Division, and Remainder
While addition and subtraction can be done on any register, multiplication and division do not. Multiplication and division always assume AX as the place holder. The format is as follows:
mul x ; --> If x is 8-bit, then AX = AL * x; ; if x is 16-bit, then DX:AX = AX * x; div x ; --> If x is 8-bit, then AL = AX / x, AH stores the remainder; ; if x is 16-bit, then AX = DX:AX / x, DX stores the remainder;
See, that's different depending on the source. As far as I recall, you cannot have variables for x in 8086. In 80286 or above you could (but again you must mention the xxxx ptr modifier). If you'd like to multiply or divide by constants (i.e. x is a constant), you'll need to load the constants into one of the registers (especially 8086). In 80286, you probably could. Anyone may correct this. I'm not quite remember.
Doing division is the same. The result of division is always rounded down. The nice extra is that we can obtain the remainder for free.
If there is an overflow in multiplication, the overflow flag will be set. There is no extra instruction like adc or sbb. So, you'll want to have an extra caution on this. Similarly, if you divide a number by 0, you'll likely to trigger an error. In windows machines this may cause a blue screen of death. So, watch out.
Note: mul and div will treat every numbers as positive. If you have negative values, you'll need to replace them imul and idiv respectively.
Increment and Decrement
Often times, we'd like to incrementing something by 1 or decrement thing by 1. You can use add x, 1 or sub x, 1 if you'd like to, but Intel x86 assembly has a special instruction for them. Instead of add x, 1 we use inc x. These are equivalent. Likewise in subtraction, you can use dec x for subtitution. Beware that neither inc nor dec instruction sets the carry flag as add and sub do.
A Nice Tip
The arithmatic operations can have special properties. For example: add x, x is actually equal to multiplying x by 2. Similarly, sub x, x is actually setting x to 0. In 8086 processor, these arithmatic is faster than doing mul or doing mov x, 0. Ha! Even more, its code size is smaller. No wonder why the assembly wizards often fond of this subtitution.
OK, I think that's all for now. See you next time. The next chapter would be about bitwise operation.
Where to go
Roby Joehanes © 2001