8086 Assembly Program to Divide Two 16-bit Numbers

Division in 8086 assembly is the most instruction-constrained of the four arithmetic operations. Unlike addition or subtraction, the DIV instruction does not let you choose which registers hold the dividend and quotient — the hardware fixes those roles permanently. Understanding why the registers are wired this way is the key to writing correct division programs and avoiding the dreaded divide overflow exception. This post walks through a working implementation in three assembler environments: MASM/TASM, emu8086, and NASM.

Prerequisites: Familiarity with 8086 registers and the segment setup pattern. New to 8086 assembly? Read Understanding DW and DB in 8086 Assembly first.


The Problem: Dividing 4444h by 0002h

We want to divide 4444h (17476 decimal) by 0002h (2 decimal) and store the quotient. Expected quotient: 2222h (8738 decimal), remainder: 0000h. Quick mental check: 17476 ÷ 2 = 8738 with no remainder. Always confirm expected output before running — a wrong expected value means you are debugging the wrong thing.

💡 Side note — how the DIV instruction works: For a 16-bit division, the 8086 hardware treats the register pair DX:AX as a single 32-bit dividend. The upper 16 bits come from DX, the lower 16 bits from AX. After DIV executes: the quotient lands in AX and the remainder lands in DX. You cannot choose different registers — this is hardwired into the CPU.

Version 1 — MASM / TASM (Classic DOS Toolchain)

This is the canonical version assembled with Microsoft MASM or Borland TASM. The full assembler, linker, and debugger session is shown in the Output section below.

data segment
    a dw 4444h      ; 16-bit dividend
    b dw 0002h      ; 16-bit divisor
    c dw ?          ; 16-bit variable to store the quotient
data ends

code segment
assume ds:data, cs:code
start:
    mov ax, data    ; Load data segment address into AX
    mov ds, ax      ; Initialize DS register

    mov ax, a       ; Load dividend (4444h) into AX
    xor dx, dx      ; Clear DX -- mandatory for a clean 16-bit divide
    mov bx, b       ; Load divisor (0002h) into BX
    div bx          ; AX = AX / BX (quotient), DX = remainder

    mov c, ax       ; Store quotient in 'c'

    int 3           ; Halt for debugging

code ends
end start
⚠️ Common mistake — not zeroing DX before DIV: The DIV BX instruction treats DX:AX as a 32-bit dividend. If DX contains leftover garbage from a previous instruction — which it often does — the effective dividend becomes a huge 32-bit number, not the 16-bit value you intended. Worse, if the quotient of that 32-bit ÷ 16-bit division does not fit in 16 bits, the CPU raises a Divide Overflow exception (INT 0) and your program crashes. Always add xor dx, dx (or mov dx, 0) immediately before DIV when dividing a plain 16-bit number.

Division in assembly is a contract: you bring the dividend in DX:AX, the divisor in any general-purpose register, and the CPU delivers quotient in AX and remainder in DX — no negotiation. 😄



Step-by-Step Explanation

  1. Data Segment:
    • a is initialized with 4444h (17476 in decimal) — the dividend.
    • b is initialized with 0002h (2 in decimal) — the divisor.
    • c is left uninitialized to store the quotient.
  2. Code Segment:
    • The MOV AX, DATA and MOV DS, AX instructions set up the data segment.
    • The dividend is moved to the AX register, and the divisor is placed in the BX register.
    • The DIV BX instruction divides the 32-bit value in DX:AX by BX. The quotient is stored in AX and the remainder in DX.
    • The quotient is then stored in memory variable c.
    • INT 3 is used to pause execution for debugging.
💡 Side note — why does DIV take only one operand? Unlike ADD AX, BX where both operands are explicit, DIV BX takes only the divisor as an explicit operand. The dividend (DX:AX) and the result destinations (AX for quotient, DX for remainder) are implicit — the instruction encoding assumes them. This is a deliberate design: division always produces two results (quotient and remainder), and the 8086 instruction format can only encode one explicit register, so Intel fixed the other registers in silicon.

Flowchart


Output

C:TASM>masm an16div.asm
Microsoft (R) Macro Assembler Version 5.00
Copyright (C) Microsoft Corp 1981-1985, 1987.  All rights reserved.
 
Object filename [an16div.OBJ]:
Source listing  [NUL.LST]:
Cross-reference [NUL.CRF]:
 
  50402 + 450254 Bytes symbol space free
 
      0 Warning Errors
      0 Severe  Errors
 
C:TASM>link an16div.obj
 
Microsoft (R) Overlay Linker  Version 3.60
Copyright (C) Microsoft Corp 1983-1987.  All rights reserved.
 
Run File [AN16DIV.EXE]:
List File [NUL.MAP]:
Libraries [.LIB]:
LINK : warning L4021: no stack segment
 
C:TASM>debug an16div.exe
-g
 
AX=2222  BX=0002  CX=0022  DX=0000  SP=0000  BP=0000  SI=0000  DI=0000
DS=0B97  ES=0B87  SS=0B97  CS=0B98  IP=0011   NV UP EI PL NZ NA PO NC
0B98:0011 CC            INT     3
-d 0B97:0000
0B97:0000  44 44 02 00 22 22 00 00-00 00 00 00 00 00 00 00   DD..""............
0B97:0010  B8 97 0B 8E D8 A1 00 00-8B 1E 02 00 F7 F3 A3 04   ................
0B97:0020  00 CC 86 72 FF 77 15 8A-86 70 FF 2A E4 50 B8 FD   ...r.w...p.*.P..
0B97:0030  05 50 FF 36 24 21 E8 77-63 83 C4 06 FF 36 24 21   .P.6$!.wc....6$!
0B97:0040  B8 0A 00 50 E8 47 5E 83-C4 04 5E 8B E5 5D C3 90   ...P.G^...^..]..
0B97:0050  55 8B EC 81 EC 84 00 C4-5E 04 26 80 7F 0A 00 74   U.......^.&....t
0B97:0060  3E 8B 46 08 8B 56 0A 89-46 FC 89 56 FE C4 5E FC   >.F..V..F..V..^.
0B97:0070  26 8A 47 0C 2A E4 40 50-8B C3 05 0C 00 52 50 E8   &.G.*[email protected].
-q
 
C:TASM>

The key register to confirm is AX=2222 — the quotient of 4444h ÷ 0002h. DX=0000 confirms the division was exact with no remainder.


Understanding the Memory Dump

The memory dump displayed in the DEBUG session shows the contents of memory after running the program. Let’s analyze the key parts.

Memory Dump Output:

0B97:0000  44 44 02 00 22 22 00 00-00 00 00 00 00 00 00 00   DD.."".........

Summary of the Memory Dump Analysis:

AddressHex ValuesInterpretation
0B97:000044 44Dividend (a) = 4444h (Hex) = 17476 (Decimal) — little-endian
0B97:000202 00Divisor (b) = 0002h (Hex) = 2 (Decimal) — little-endian
0B97:000422 22Quotient (c) = 2222h (Hex) = 8738 (Decimal)
0B97:0006 – 000F00 00 …Unused / uninitialized memory
💡 Side note — little-endian and why 4444h looks like 44 44: The 8086 stores multi-byte values with the least significant byte first. For 4444h, both bytes happen to be 44h, so little-endian and big-endian look the same. For 0002h, the low byte 02 is stored first, followed by 00 — hence 02 00 in the dump. For 2222h, both bytes are again 22h. Whenever you see a 16-bit value in a memory dump, mentally swap the two bytes to read it correctly.

Version 2 — emu8086 (Windows Emulator)

The emu8086 version uses COM format via the #make_COM# directive. It explicitly zeroes DX before the division — making the program safe even if DX was left dirty by earlier code. The remainder is also stored in a separate variable so you can inspect it in the Memory panel.

Tested with: emu8086 v4.08 on Windows 10.

; emu8086 version -- 8086 Assembly Program to Divide Two 16-bit Numbers
; Assemble as COM file using: #make_COM#

#make_COM#

org 100h            ; COM programs begin at offset 100h

; --- Code ---
start:
    mov ax, a       ; Load dividend into AX
    xor dx, dx      ; Clear DX -- mandatory before DIV to avoid divide overflow
    mov bx, b       ; Load divisor into BX
    div bx          ; AX = quotient, DX = remainder

    mov c, ax       ; Store quotient
    mov rm, dx      ; Store remainder

    mov ax, 4c00h   ; Exit via DOS
    int 21h

; --- Data declarations ---
a  dw 4444h         ; Dividend
b  dw 0002h         ; Divisor
c  dw 0000h         ; Quotient
rm dw 0000h         ; Remainder
⚠️ Common mistake — placing mov bx, b before xor dx, dx: The order of the three setup instructions matters. If you write mov bx, b first and then xor dx, dx, the result is the same — BX and DX are independent registers. But many students then accidentally write mov dx, b instead, which puts the divisor in DX (part of the dividend!) and BX is never set. The safest habit is the order shown above: load dividend into AX, zero DX, then load divisor into BX.

How to Run in emu8086

  1. Open emu8086, paste the code and click Compile and Run (or press F5).
  2. After execution stops, check the Registers panel. You should see AX = 2222 (quotient) and DX = 0000 (remainder).
  3. Open the Memory panel and navigate to variable c. You should see bytes 22 22 — that is 2222h in little-endian order. Variable rm should show 00 00.

Version 3 — NASM (Modern Open-Source Assembler)

The NASM version uses flat binary COM format. As always with NASM, memory operands require square brackets. DX is explicitly zeroed before the divide.

Tested with: NASM 2.16.01, executed in DOSBox 0.74-3.

; NASM version -- 8086 Assembly Program to Divide Two 16-bit Numbers
; Assemble:  nasm -f bin an16div.asm -o an16div.com
; Run:       Place an16div.com in DOSBox and execute it

bits 16             ; 16-bit mode
org  100h           ; COM file origin

; --- Code ---
start:
    mov ax, [a]     ; Load dividend into AX (4444h)
    xor dx, dx      ; Clear DX -- required for a clean 16-bit divide
    mov bx, [b]     ; Load divisor into BX (0002h)
    div bx          ; AX = quotient (2222h), DX = remainder (0000h)

    mov [c], ax     ; Store quotient
    mov [rm], dx    ; Store remainder

    mov ax, 4c00h   ; Exit via DOS
    int 21h

; --- Data ---
a  dw 4444h         ; Dividend
b  dw 0002h         ; Divisor
c  dw 0000h         ; Quotient storage
rm dw 0000h         ; Remainder storage
💡 Side note — storing the remainder is free: Most textbook examples only store the quotient and ignore the remainder. But DX holds it right after DIV and a single mov [rm], dx captures it at zero extra cost. In real programs — modulo operations, BCD conversion, unit extraction — the remainder is often the more useful result. Get in the habit of capturing both.

Build and Run Steps

# Assemble to flat binary COM format
nasm -f bin an16div.asm -o an16div.com

# Run inside DOSBox
# Inside DOSBox:
#   mount c .
#   c:
#   an16div.com

Register State After Execution (All Three Versions)

After DIV BX completes:

RegisterValueMeaning
AX2222Quotient of 4444h ÷ 0002h
DX0000Remainder — zero because 4444h is exactly divisible by 2
BX0002Divisor — unchanged after DIV
CF / OFundefinedDIV leaves Carry and Overflow flags in an undefined state
⚠️ Common mistake — trusting CF or OF after DIV: The 8086 DIV instruction leaves the Carry Flag and Overflow Flag in an undefined state. Do not use JC or JO to check for errors after division. The only reliable way to detect a divide overflow is to ensure the quotient will fit in AX before calling DIV — i.e., confirm that the dividend (DX:AX) divided by the divisor is less than FFFFh. If it isn’t, the CPU raises INT 0 before you even get a result.

Frequently Asked Questions

Why does DIV take only one operand — why not DIV AX, BX?

Because DIV always produces two results: quotient and remainder. The 8086 instruction encoding only supports one explicit register operand per instruction. To handle two outputs, Intel hardwired the destinations into the silicon: quotient always goes to AX, remainder always goes to DX. The one operand you do supply is the divisor. The dividend (DX:AX) is implicit. So DIV BX means: divide the 32-bit value in DX:AX by BX, put the quotient in AX, put the remainder in DX. There is no two-operand form of DIV on the 8086.

How does the CPU know AX is the dividend? Can I use a different register?

No — AX (and DX) are hardwired as the dividend for 16-bit division. This is not a software convention you can override; it is encoded in the microcode of the processor. Whatever value is in AX when DIV executes is the low 16 bits of the dividend, and whatever is in DX is the high 16 bits. If you load your dividend into CX instead of AX before calling DIV BX, you will divide whatever garbage happens to be in AX — not your intended value.

What happens if I divide by zero?

The CPU raises a Divide Error exception (also called INT 0 or #DE). On a real DOS machine this immediately aborts the program with a “Divide overflow” message. The same exception fires if the quotient is too large to fit in AX — for example, dividing FFFF0000h (in DX:AX) by 0002h produces a quotient of 7FFF8000h, which does not fit in 16 bits. There is no way to catch this in the program without adding a pre-check: compare the dividend’s high word in DX against the divisor before calling DIV, and handle the overflow case manually.


Conclusion

16-bit division in 8086 assembly is more constrained than addition or subtraction — the dividend register pair, quotient destination, and remainder destination are all fixed by the hardware. The two things that cause the most real-world bugs are forgetting to zero DX before the divide (causing a spurious overflow) and expecting CF or OF to be meaningful afterward (they are not). Get those two habits right — always xor dx, dx, never trust flags after DIV — and division is as reliable as any other arithmetic instruction.


See Also

4 thoughts on “8086 Assembly Program to Divide Two 16-bit Numbers”

        1. but how does the assembler know that it is to take ax as dividend? What if I used another register place of ax? Or what if the ax assignment statement was some line before where it is right now?

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.