UMBC CMSC 313 -- Sequential Instructions, Part II Previous | Next


Sequential Instructions, Part II

Addition and subtraction are easy. Multiplication and division are a little more complex, because the possibilities of special cases are greater.

Multiplication

Let's count digits! 99 has two digits. 99 times 99 is 9801. Two digits times two digits will equal four digits. Or at least that is the worst case. 8 bits (255 or FF16) time 255 equals 65025, or FF0116 or 16 bits. To make a general rule about the worst case, n bits times n bits equals 2n bits.

In assembly language, both arguments have to be the same size, even when multipling or dividing (sort of!) Also, when planning, we must consider the worst case as the only case, because the best case will work within the worst case.

So we multiple 8 bits time 8 bits or 16 bits times 16 bits or 32 bits times 32 bits. In assembly language that is the size of the containers. We do not care about the value, just does it fit in the container. When we put a one into a 32 bit container then we are choosing to do a 32 bit multiple. The rule for multiplication is that when multiplying two n-bit containers, the size of the answer 2n. (Since division is the inverse of multiplication, 2n divided by n, the answer is n.)

Multiplication is truly a shortcut for addition, while division is a shortcut for subtraction. Four times three is equal to four plus four plus four. Division is 1) twelve minus four 2) eight minus four and 3) four minus four. Forunately, the hardware designers now let us do it with a single instruction.

However, it is still a little more complex than before. First of all, it takes longer. There is also the problem with the size of numbers (even more so than addition and subtraction!) 99 times 99 is 9801. A byte times a byte can result in a word! Multiplying two n-digit numbers will probably result in a 2n-digit number. Division goes the other direction, but is even more difficult because we have to keep track of the remainder. Then comes the wrinkle of signs!

So when you multiply two sixteen bit numbers, expect the answer to require more bits! The 80x86 CPU requires special registers for this:

 

Operand SizeMulti-
plicand
Multiplier Product
BYTE AL Reg or MemoryAX
WORD AX Reg or Memory
Reg, immed
Reg, Reg, immed
AX (low) and DX (high)
DWORD EAX Reg or memory
Reg, immed
Reg, Reg, immed
EAX (low) and EDX (high)

Where did this come from???? Well, it starts only with the 386 and works in the 16- or 32-bit mode on signed values only. It is a messy attempt to enhance things. Because of the nature of this mess, it is better not to use it (my opinion!)

Division is similar (except that it does not allow a constant!):

Operand Size Dividend Divisor Quotient Remainder
BYTE AX Reg or Memory AL AH
WORD AX and DX Reg or Memory AX DX

You can not use a constant operand with (sometimes )multiplication or (always) division. Use the mov instruction to put a constant into the appropriate register when needed.

Did I mention something about the sign? There are different instructions based on whether or not it is a signed or unsigned operation. That leads us to the following:

Instruction Description
mul unsigned multiplication
div unsigned division
imul signed multiplication
idiv signed division

Yes, but....

An Example

OK, lets put it together:

To calculate the miles per gallon, you must, divide the number of miles driven by number of gallons of gas used. We must analyze the data to make sure we code it correctly. First, what kind of numbers are we talking about?

They are integers (because you don't know how to do floating point yet!) It is impossible to drive -100 miles and add -20 gallons of gas, so we have unsigned values. It is probable that the number of miles driven exceeds 256 miles and we will assume that it will not exceed 65,535 miles. That means the numbers can not fit into one byte and will not exceed two bytes.

When we use C to code this problem, we get something like:

	short	miles;
	short	gallons;
	short	mpg;

	mpg = miles / gallons;
Assembly language requires the programmer to do the dirty work! First of all, we must get the operands into the correct size. The quotient must be 32 bits and the divisor must be 16 bits. When we are done, we will have a 16 bit number.
	mov  ax, word [miles]
	cwd             	 ;ax is assumed
	idiv word [gallons]      ;ax is assumed
	mov  word [mpg], ax

8-/16-/32-bit Versions

	
	
section .data
miles8	db	250
gal8	db	12
miles16 dw	328
gal16	dw	15
miles32 dd	328
gal32	dd	15

msg	db	'answer is = %d', 10, 0

section .bss
mpg8	resb	1
mpg16   resw	1
mpg32	resq	1


section .text
    global main
    extern printf

;; just like main in C -- if linking with gcc, must be main, otherwise does not have to.

main:                                 ;tell linker entry point

;; 8-bit division

	mov	eax, 0			; clear high 8-bits of 16-bit value
	mov	al, [miles8]		; 8-bit to 8-bit
	div	byte [gal8]		; NOTE: remainder in ah
	mov	byte [mpg8], al		; save answer

	; output results ;;should be a function
	mov	eax, 0			; make sure no garbage in high order bits
	mov	al, byte [mpg8]		; get answer

	; set up printf
	push	eax			; set up value -- 4 bytes on stack
	push	dword msg		; set up print specification -- 4 more
	call	printf
	add	esp, 8			; take off what you put on the stack!!

;; 16-bit division
	mov	eax, 0
	mov	edx, 0			; make sure to clear the high order!
	mov	ax, [miles16]
	div	word [gal16]
	mov	word [mpg16], ax
	
	; output results ;;should be a function
	mov	eax, 0			; make sure no garbage in high order bits
	mov	ax, word [mpg16]	; get answer

	; set up printf
	push	eax			; set up value -- 4 bytes on stack
	push	dword msg		; set up print specification -- 4 more
	call	printf
	add	esp, 8			; take off what you put on the stack!!

;; 32-bit division
	mov	eax, 0
	mov	edx, 0			; make sure to clear the high order!
	mov	eax, [miles32]
	mov	ebx, dword [gal32]
	div	ebx			; use a register this time, instead of memory
	mov	dword [mpg32], eax
	
	; output results ;;should be a function
	mov	eax, dword [mpg32]	; get answer -- same as 16-bit, not necessary here

	; set up printf
	push	eax			; set up value -- 4 bytes on stack
	push	dword msg		; set up print specification -- 4 more
	call	printf
	add	esp, 8			; take off what you put on the stack!!
	
;; The final part of the program must be a call to the operating system to exit
;; the program.
        mov     ebx,0   ;successful termination of program
        mov     eax,1   ;system call number (sys_exit)
        int     0x80    ;call kernel

;; Notice the file just ends.
  

To find out how many weeks and days are in a period of days, we could use unsigned bytes and we have two math operations to perform, unsigned division and modulus!

	weeks = nrDays / 7;
	days  = nrDays % 7;
Interestingly, we only have to do division here, because the remainder is always calculated at the same time, so we just have to save it:
	mov al, [nrDays]
	sub ah, ah      ;don't sign extend because that 
	                ;  could change the value, just 
                        ;  clear out the high part!
	mov bl, 7
	div bl          ;can't use a constant here!
	mov [weeks], al
	mov [days], ah  ;the remainder is the portion 
                        ;  of a week

Now an example of multiplication.

;
; One more example, this time with multiplication of a unsigned number
;
	mov	ax, 24	; there are 24 hours in a day
	mov	bx, 7	; How many hours in a week?
	mul	bx	; Calculate the answer
	push	eax
	push	string3
	call	printf

Let's see it all

Very Important Reminders


Previous | Next

©2004, Gary L. Burt