Multiplying two 32-bit numbers on the 16-bit 8086 is the most involved of the basic arithmetic operations. Because the processor only has a 16×16 → 32 multiplier, a 32×32 multiplication must be broken into four 16×16 partial products and their results accumulated with careful carry propagation. The final 64-bit result spans eight bytes across four 16-bit words. This post walks through a working implementation in three environments: MASM/TASM, emu8086, and NASM.
Prerequisites: Familiarity with 16-bit multiplication (MUL) and carry propagation. Read 8086 Assembly Program to Multiply Two 16-bit Numbers first.
The Problem: Multiplying 12345678h by 12345678h
We want to compute 12345678h × 12345678h (305,419,896 squared). The 64-bit result is 014B66DC_1DF4D840h. The debugger memory dump confirms ghi = 40 D8 F4 1D DC 66 4B 01 — reading as little-endian 64-bit: lower 32 bits 1DF4D840h, upper 32 bits 014B66DCh ✓.
Version 1 — MASM / TASM (Classic DOS Toolchain)
data segment abc dd 12345678H ; 32-bit multiplicand def dd 12345678H ; 32-bit multiplier ghi dq ? ; 64-bit result (dq = define quadword) data ends code segment assume cs:code, ds:data start: mov ax, data mov ds, ax ; Partial product 1: abc_lo * def_lo -> ghi[0] and carry into CX mov ax, word ptr abc mul word ptr def mov word ptr ghi, ax ; Store bits 0-15 of result mov cx, dx ; CX holds carry (bits 16-31, partial) ; Partial product 2: abc_hi * def_lo -> accumulate into CX and BX mov ax, word ptr abc+2 mul word ptr def add cx, ax ; Accumulate into CX mov bx, dx ; BX = carry from this partial product jnc move add bx, 0001H ; Propagate carry into BX move: ; Partial product 3: abc_lo * def_hi -> accumulate into CX and BX mov ax, word ptr abc mul word ptr def+2 add cx, ax mov word ptr ghi+2, cx ; Store bits 16-31 mov cx, dx jnc ma add bx, 0001H ma: ; Partial product 4: abc_hi * def_hi -> accumulate CX and DX mov ax, word ptr abc+2 mul word ptr def+2 add cx, ax jnc mb add dx, 0001H mb: add cx, bx mov word ptr ghi+4, cx ; Store bits 32-47 jnc mc add dx, 0001H mc: mov word ptr ghi+6, dx ; Store bits 48-63 int 3 code ends end start
dw instead of dq for the result: The maximum 32×32 product is FFFFFFFF² = FFFFFFFE_00000001h, which needs 64 bits (8 bytes). Declaring ghi dw ? reserves only 2 bytes. The program writes 8 bytes across ghi through ghi+7, silently corrupting memory beyond the variable. Always declare the result with dq (define quadword, 8 bytes) or db 8 dup(0).
Related Links:
Step-by-Step Explanation
1. Data Segment:
- abc dd 12345678H — 32-bit multiplicand.
- def dd 12345678H — 32-bit multiplier (same value).
- ghi dq ? — 64-bit result buffer (8 bytes).
2. Code Segment — Four Partial Products:
- abc_lo × def_lo: Lowest 16×16 product. Low 16 bits stored directly to
ghi[0]; high 16 bits held in CX. - abc_hi × def_lo: Middle-low partial. Result accumulated into CX; carry propagated into BX.
- abc_lo × def_hi: Middle-high partial. CX finalized and stored to
ghi[2]; carry continues into BX and DX. - abc_hi × def_hi: Highest partial. Results stored to
ghi[4]andghi[6].
3. Overall Process:
- Four 16×16 partial products are computed and accumulated with careful carry tracking.
- The 64-bit result is assembled across
ghi,ghi+2,ghi+4, andghi+6.
Output
C:TASM>masm an_32mul.asm
Microsoft (R) Macro Assembler Version 5.00
Copyright (C) Microsoft Corp 1981-1985, 1987. All rights reserved.
0 Warning Errors
0 Severe Errors
C:TASM>link an_32mul.obj
LINK : warning L4021: no stack segment
C:TASM>debug an_32mul.exe
-g
AX=5A90 BX=0626 CX=66DC DX=014B SP=0000 BP=0000 SI=0000 DI=0000
DS=0B97 ES=0B87 SS=0B97 CS=0B98 IP=0052 NV UP EI PL NZ NA PO NC
0B98:0052 CC INT 3
-d 0B97:0000
0B97:0000 78 56 34 12 78 56 34 12-40 D8 F4 1D DC 66 4B 01 [email protected].
-q
Understanding the Memory Dump
0B97:0000 78 56 34 12 78 56 34 12 40 D8 F4 1D DC 66 4B 01
| Address Range | Content | Description |
|---|---|---|
0B97:0000–0007 | 78 56 34 12 78 56 34 12 | abc and def, both 12345678h in little-endian |
0B97:0008–000B | 40 D8 F4 1D | Lower 32 bits of result: 1DF4D840h |
0B97:000C–000F | DC 66 4B 01 | Upper 32 bits of result: 014B66DCh |
Full 64-bit result: 014B66DC_1DF4D840h — which equals 12345678h² = 305,419,896² = 93,281,182,073,217,024 decimal.
Version 2 — emu8086 (Windows Emulator)
Tested with: emu8086 v4.08 on Windows 10.
The emu8086 version translates the complex 32-bit multiplication logic into a format ready for the built-in debugger. This version uses the standard COM template, making it easy to single-step through each partial product and carry propagation.
; emu8086 version -- 8086 Assembly Program to Multiply Two 32-bit Numbers
#make_COM#
org 100h
; --- Code ---
start:
mov ax, word ptr abc
mul word ptr def
mov word ptr ghi, ax
mov cx, dx
mov ax, word ptr abc+2
mul word ptr def
add cx, ax
mov bx, dx
jnc move
add bx, 0001H
move:
mov ax, word ptr abc
mul word ptr def+2
add cx, ax
mov word ptr ghi+2, cx
mov cx, dx
jnc ma
add bx, 0001H
ma:
mov ax, word ptr abc+2
mul word ptr def+2
add cx, ax
jnc mb
add dx, 0001H
mb:
add cx, bx
mov word ptr ghi+4, cx
jnc mc
add dx, 0001H
mc:
mov word ptr ghi+6, dx
mov ax, 4c00h
int 21h
; --- Data (after code to avoid execution as instructions) ---
abc dd 12345678H
def dd 12345678H
ghi dq 0
Version 3 — NASM (Modern Open-Source Assembler)
Tested with: NASM 2.16.01, DOSBox 0.74-3.
For modern environments, the NASM version provides a clean implementation with explicit memory references. This version is ideal for users who prefer working with the command line or integrating assembly into a modern development pipeline.
; NASM version -- 8086 Assembly Program to Multiply Two 32-bit Numbers
; nasm -f bin an32mul.asm -o an32mul.com
bits 16
org 100h
start:
mov ax, [abc]
mul word [def]
mov [ghi], ax
mov cx, dx
mov ax, [abc+2]
mul word [def]
add cx, ax
mov bx, dx
jnc move
add bx, 1
move:
mov ax, [abc]
mul word [def+2]
add cx, ax
mov [ghi+2], cx
mov cx, dx
jnc ma
add bx, 1
ma:
mov ax, [abc+2]
mul word [def+2]
add cx, ax
jnc mb
add dx, 1
mb:
add cx, bx
mov [ghi+4], cx
jnc mc
add dx, 1
mc:
mov [ghi+6], dx
mov ax, 4c00h
int 21h
; --- Data ---
abc dd 12345678H
def dd 12345678H
ghi dq 0
Frequently Asked Questions
Why does 32×32-bit multiplication need a 64-bit result?
The maximum 32-bit value is FFFFFFFFh. Squaring it gives FFFFFFFE_00000001h, which requires 64 bits. Just as 16×16 overflows into DX:AX (32 bits), 32×32 overflows into an 8-byte quadword. The 8086 has no native 32×32 instruction, so the software must assemble the 64-bit result from four 16×16 partial products.
Why are there multiple JNC / carry-increment sequences?
Each time two partial products are added together with ADD CX, AX, the sum can overflow 16 bits. Each such overflow must be counted and propagated into the next higher word of the result. The pattern JNC skip / ADD BX, 1 is the manual carry-propagation mechanism — BX and DX accumulate carries that are folded in when their corresponding result words are written.
What is dq and why is it needed for the result?
dq stands for “define quadword” and reserves 8 bytes — enough to hold a 64-bit value. Using dw (2 bytes) or dd (4 bytes) for ghi means the program writes beyond the declared variable into adjacent memory, silently corrupting other data. Always match the result variable size to the widest possible product.
Conclusion
32-bit multiplication on the 8086 decomposes into four 16×16 partial products that are accumulated into a 64-bit result. The algorithm is more involved than simpler arithmetic operations, but every step follows directly from the schoolbook long-multiplication method applied at the word level. The critical correctness requirements are: use dq for the result, propagate every carry between partial products, and store all four 16-bit words of the result.
SHOKAM
i am still getting some interrupts
yeah me too
why do you use word ptr? and there is no way to use eax, ebx register to do more easy?