CHAPTER 7 - Arithmetic instructions, more on flags


In this chapter you will learn how to perform basic math operations in assembly language. Then you will get deeper into how processor does it and thus learn more on flags.


7.1. Addition and substraction
Simplest case of addition is addition of one, called "incrementing". For example, if we increment variable holding value 5, it will contain 6 etc.

Instruction which performs incrementing is inc (obvious why). It has one operand, which tells what should be incremented (eg. to what will 1 be added). Operand can be register or memory variable. It can't be constant, of course, because such instruction, even if it would exist, wouldn't have any effect. Example of incrementing:
mov ax,5
inc ax	     ;increment (add 1 to) value in ax
  ;here ax holds value 6

I think it is clear (it isn't, you'll see later why).

Substracting of value 1 is called "decrementing". Decrementing is opposite of incrementing. Instruction which performs decrementing is dec. Example:
mov ax,5
inc ax	     ;increment (add 1 to) value in ax
  ;here ax holds value 6
dec ax	     ;decrement (substract 1 from) value in ax
  ;here ax holds value 5 again
terms: incrementing (adding 1), decrementing (substracting 0)
instructions: inc, dec
If you wan't to add or substract more than 1, you can use more incs or decs, but that is quite ugly way, requires more typing, and code is big and slow. So there is instruction which can add any value, this instruction is add. It takes two arguments, first one is destination eg. value to which will be added, and second is value to be added. Argument types are same as for mov, first can be register or memory variable, second can be constant, register or memory variable (if first one isn't memory variable, remember - one instruction can't access two memory locations). Example:
mov ax,5
add ax,5
;here ax contains 10

Another example:
mov ax,5
mov bx,5
add bx,[five]
add ax,bx
;here ax contains 15, bx contains 10
five dw 5

Instruction for substracting is sub. It is exact opposite of add, everything is same for it as for add.
mov ax,15
mov bx,10
sub bx,[five]
sub ax,bx
;here ax contains 10, bx contains 5
five dw 5



7.2. Overflows
There are some cases with addition and substraction that I haven't already mentioned. For example if you try to add 10 to byte sized variable containing 250 (biggest number byte sized variable can hold is 255). In such case, we say that overflow has occured.

But question is, what happens with result of operation that has overflown. When upper limit of variable is crossed, then result of operation will be rest of value to be added. We can say the operation will be "wrapped" from max value to minimal value. For example:
byte 255 + 1 = 0
byte 255 + 2 = 1
byte 254 + 3 = 1
byte 250 + 10 = 5
byte 255 + 255 = 254
word 65535 + 1 = 0
word 65535 + 65535 = 65534
etc.

There is also other case, when result of operation falls below lower limit (which is 0 for any size of variable). In this case result of operation will be wrapped from lower limit to upper limit. This case is called underflow. For example:
byte 0 - 1 = 255
byte 0 - 255 = 1
byte 254 - 254 = 0
byte 254 - 255 = 255
etc.

NOTE: Word oveflow is usually used for both overflow and underflow.
Terms: Overflow, Underflow
We also need to know how to check if overflow has occured after performing operation to prevent bugs. For this, flags are used. I mentioned flags in chapter 5.3. We used flags for checking results of comparison at conditional jumps, and I also said that there shouldn't be any instrcutions between comparison and jumps, because many instructions change flags (of course you can place instruction there if you are sure it won't change any needed flag). Arithmetic instructions add and sub use one bit of flags called CF (carry flag). If overflow occurs, then they set it to 1, otherwise they set it to 0. You can test carry flag with conditional jumps jc and jnc (see chapter 5.3 about conditional jumps). jc jumps if carry flag is set, jnc jumps if carry flag is not set.

Here is example of testing overflows:
add ax,bx
jc overflow
no_overflow:

sub cx,dx
jc underflow
no_underflow:
carry flag (CF) - ont bit (flag) of "flags" register
conditional jump instructions: jc, jnc



7.3. Zero Flag
Instructions inc and dec doesn't set CF, so you can't test overflow using CF with them. But there is another rule that can be used to prevent overflow with inc and dec. This rule is that when result of operation is zero, then flag called "zero flag" (ZF) is set. This flag is tested with jz (jump if zero flag is set) and jnz (jump if zero flag is clear) conditional jump instructions.

With this you can create loops, eg. repeat some part of code several times.
For example code
  org 256
  mov cx,5
here:
  mov dl,'a'
  mov ah,2
  int 21h
  dec cx
  jnz here

  int 20h
will write
aaaaa
NOTE: You can optimize that part of code to
  org 256
  mov cx,5
  mov dl,'a'
  mov ah,2
here:
  int 21h
  dec cx
  jnz here

  int 20h
because value of dl and ah isn't changed anywhere in loop, so we don't have to set it each time.

Not only add and sub instructions set ZF if result is zero (and clear it otherwise). All basic arithmetic instructions do this. For now, you know these arithmetic instructions: add, sub, and, xor and or. So afer any of these instruction, ZF tells you if destination (first argument) of operation holds 0. For example, You can use this behavior to check if value of register is 0. Before, you would do this with
cmp ax,0
jz ax_is_zero
but you can also use "or" to do this:
or ax,ax
jz ax_is_zero
Or won't change ax, because 1 ored with 1 is 1, and 0 ored with 0 is 0. Reread chapter 6 if you don't comprehend that. Btw, this was used on older computers because such code was faster and is few bytes smaller than with cmp.


7.4. Carry flag, more binary arithmetic instructions
I mentioned carry flag a little in connection with overflow. But CF is really general-purpose flag because it can be tested easily (jc,jnc and few more), and it's value can be easily set. You will find many more uses of CF later.

How to set CF? There are two instructions for this. stc stays for "SeT Carry", and so it "sets" carry flag (eg. sets it's value to 1), so jc jump is taken and jnc is not taken etc etc, you should understand this aleady. Instruction clc (CLear Carry) clears CF.

When we know how to work with CF, we can learn about rest of bit arihmetic operations. First will be shl. It shift bits of register to left, eg. 0th bit becomes 1st, 1st becomes 2nd etc. Last bit (7th of byte or 15th of word) is moved to CF. First bit becomes 0. This way (if highest bit wa zero) we have multiplied the shifted register by 2.

Before shifting: bit7:bit6:bit5:bit4:bit3:bit2:bit1:bit0
After shifting: bit6:bit5:bit4:bit3:bit2:bit1:bit0:0, CF=bit7

To explain why number is multipied by 2. If you remember beginning of chapter 6, you know that number before shifting is 128*bit7 + 64*bit6 + 32*bit5 + 16*bit4 + 8*bit3 + 4*bit2 + 2*bit1 + bit0 so after shifting it becomes 128*bit6 + 64*bit5 + 32*bit4 + 16*bit3 + 8*bit2 + 4*bit1 + 2*bit0 which is 2*(64*bit6 + 32*bit5 + 16*bit4 + 8*bit3 + 4*bit2 + 2*bit1 + bit0). So if highest bit is zero, then number is multiplied by two. This way we can easily multiply by powers of two (2, 2^2=4, 2^3=8, 2^4=16 etc.). Also upper bit is stored in CF so we can test overflow of mulitplication with jc and jnc

Usually we want to shift more than once (multiply by 4, 8, 16...), so shl takes second argument, which tells how many times we want to shift. If we shift by number greater than 1, CF contains 1 if ANY of discarded bits (x highest bits, where x is value we are shifting by) contained 1. This way we can still check for overflow. If you are beginner, don't care about overflow checking too much, you probably won't do it anyway :) (and your program will probably contains bugs then)

There is one limitation to shl - it's arguments doesn't follow same rules as other instructions you know (mov,add etc.) Fisrt argument can be register or memory location, but second can be only numeric constant or CL register (really, no other).

NOTE: Orignially, at 8086 (that's 086, first of 80x86 series known as x86, like 286 or 486), there was only shl instruction which could shift by one, and so for example shl ax,3 was compiled into 3 shls. There also wasn't any shifting by register, you had to make loop for that. Fortunately 80286 had shifting by constant and by CL so it is now OK.

That was about shifting left, but there is also other type of shifting, that is shifting to right. I hope you can now imagine what it does, just few notes to it. Instruction that performs this is shr (shift right). It's effect is division by two (or powers of two) without remainder. If we shift right by two, then remainder (0 or 1) is in CF, otherwise CF beheaves like with shifting left by number higher than two: If remainder isn't 0 (at least one of discarded bits was 1) then CF is set, otherwise it is clear.


7.5. Some examples
At least we are able to make output of number (write number to screen). Too bad we can only write it in binary :). So here is our task: Write program that outputs any binary number. For now, we will hardcode number into program, eg. move it into some register as constant. Here is the source.
org 100h

mov bx,65535 ;we store number we want to display in bx
 ;(because it's not used by DOS services we use)
mov cx,16 ;we are diplaying 16 digits (bits)

;display one digit from BX each loop

display_digit:
shl bx,1
jc display_one

;display '0'
mov ah,2
mov dl,'0'
int 21h
jmp continue

;display '1'
display_one:
mov ah,2
mov dl,'1'
int 21h

;check if we want to continue
continue:
dec cx
jnz display_digit

;end program
int 20h

I hope you understand this, it's quite simple. Each loop we shift BX register left by one, so upper bit is moved to CF, then we write '0' or '1' depending on value of CF (previously upper bit of number) and continue loop until we write 16 digits (because word has 16 bits).

Example of stepping thru code:
Start: CF=16, BX=1100101000001011b
Pass1: CX=15, BX=1001010000010110b, CF=1
Pass2: CX=14, BX=0010100000101100b, CF=1
Pass3: CX=13, BX=0101000001011000b, CF=0
...
Pass14: CX=2, BX=1100000000000000b, CF=0
Pass15: CX=1, BX=1000000000000000b, CF=1
Pass16: CX=0, BX=0000000000000000b, CF=1


In my opinion, if you made it here, with (generaly) understing everything, you can consider yourself to be more than beginner, congratulations!!! There is much to learn to become well-armed assembly programmer, but you now have the sufficent base which you will only extend, with or without use of this tutorial. (But there are several parts which will be explained further which are hard to find in any tutorial)