• Please review our updated Terms and Rules here

The Great DIVide

neilobremski

Experienced Member
Joined
Oct 9, 2016
Messages
55
Location
Seattle, USA
The DIV and IDIV instructions perform integer divisions simply and relatively quickly as opposed to creating a simple loop. Let me start by showing how one could divide an integer without these instructions. The following assembly function (CALL 110) will divide BX by CX and put the quotient in AX:

Code:
0110 31C0	XOR     AX,AX
0112 39CB	CMP     BX,CX
0114 7206	JB      011C
0116 40  	INC     AX
0117 29CB	SUB     BX,CX
0119 EBF7	JMP     0112

The remainder will end up in BX. This entire procedure makes a number of jumps equal to that of the amount of times CX wholly goes into BX plus one more jump to end the loop. Considering each jump takes 16 cycles [SUP][1][/SUP], this will take over 80 cycles simply to divide 8 by 2!

Now the cycle times don't seem so bad when you look at DIV or IDIV, right? The cost of a division isn't cheap and this is why it must be avoided for tight loops in order to keep their speed up.


But at some point you're going to need to use these instructions and I often get confused as to what registers get used and how; I also tend to fail to see the "Divide Error" exception as it shows up in DEBUG.

There are two versions of DIV (and IDIV). One divides AX by a byte (8 bits) and the other divides DX:AX by a word (16 bits). In both of these the dividend is made up of words. For example, if you want to divide 8 by 2, you might think this is sufficient:

Code:
MOV AL, 8
MOV AH, 2
DIV AH

No! The result of this will actually be 0x208 divided by 2 which is an unholy Divide error. Even if you are only interested in dividing one byte value by another, you must use the entire word of AX:

Code:
MOV AX, 8
MOV BL, 2
DIV BL

Since this instruction uses a lot of registers already, it overloads their use for return values. For instance, the AX register is destroyed and for our byte divide it is returned as both the quotient and remainder (in AL and AH respectively).

Alright, now I want to address the nature of the Divide Error which is an overflow exception. Yes, even though I am down at the assembler level there are still exceptions that can disrupt the flow of the programs. These are implemented as interrupts that get called when the exception is raised.[SUP][2][/SUP] The reason for this exception may not be a divide by zero which is what I initially thought.

Remember the example above where 0x208 divided by 2 results in the divide error exception? Calculating the result shows why: 0x208 / 2 = 0x104 and 0x104 cannot fit into AL! This tells me that it is unsafe to ever try dividing a word by a byte but only safe to divide a byte by a byte using a word to store the dividend. That is: always keep AH clear for divide-by-byte and always keep DX clear for divide-by-word.

I used to think that 8088's could handle dividing 32-bit integers (for fixed point) and now I see that they cannot reliably do this. I haven't yet worked in 32-bit X86 yet but I believe the same applies for 64-bit divides.

Finally, signed integer division with IDIV requires some fancy footwork because of the issues with overflow. What if I want to divide -8 by 2? You might be tempted to try this:

Code:
MOV	AL, F8	; (0xF8 == -8)
CBW		; convert byte AL to word AX
MOV	BL, 02	;
IDIV	BL	;

That actually works but with a twist you might see coming: AH is zero! This essentially converts the word AX back into the byte AL since AH is always the remainder, not part of the quotient. Thus if you want to see the positive value, NEG AL rather than NEG AX.

Whew! Sally forth and divide and conquer!

Footnotes:
[SUP][1][/SUP]. Working on an 8088 and disregarding the cycles burned to read the instructions from memory into the prefetch queue.

[SUP][2][/SUP]. When tracing with DEBUG an exception of this kind results in some seemingly bizarre behavior: suddenly different code begins executing:

 
Back
Top