Division Instructions Cheat Sheet (x86/x86-64)
Rule of div
div operand
divides a larger implicit dividend by the operand you give.- The CPU assumes the dividend is in a fixed register pair depending on the operand size.
Operand size | Dividend location | Quotient goes to | Remainder goes to |
---|---|---|---|
8-bit | AX (16-bit register) | AL | AH |
16-bit | DX:AX (32-bit value) | AX | DX |
32-bit | EDX:EAX (64-bit value) | EAX | EDX |
64-bit | RDX:RAX (128-bit value) | RAX | RDX |
div—Unsigned Division idiv—Signed Division
The Colon in <REG1>:<REG2>
The colon in
EDX:EAX
means concatenation—the two registers are treated as if they were glued together to form one larger integer.
In 32-bit division:
EAX
is the low 32 bits.
EDX
is the high 32 bits.Together
EDX:EAX
makes a 64-bit dividend.So if:
EDX = 0x00000001
EAX = 0x00000000
then
EDX:EAX
=0x00000001_00000000
(a 64-bit number).
Why this matters
The CPU needs a bigger dividend than the divisor size, because division can produce both a quotient and a remainder.
Example:
If you
div ebx
(32-bit divisor), the CPU assumes the dividend is 64 bits inEDX:EAX
.If
EDX
is zero, you’re really just dividingEAX
byebx
.If
EDX
is nonzero, you’re dividing a larger 64-bit number.
Other cases
DX:AX → 32-bit dividend made from two 16-bit registers.
AX (alone) → 16-bit dividend for 8-bit division.
RDX:RAX → 128-bit dividend for 64-bit division in x86-64.
8-bit Division
- Dividend: AX (16-bit)
- Divisor: 8-bit register or memory
- Quotient: AL
- Remainder: AH
Example (unsigned)
Dividing .
mov ax, 20 ; Dividend = 20
mov bl, 3 ; Divisor = 3
div bl ; AX ÷ BL → AL=6, AH=2
16-bit Division
- Dividend: DX:AX (32-bit, DX = high word, AX = low word)
- Divisor: 16-bit register or memory
- Quotient: AX
- Remainder: DX
Example (unsigned)
Dividing .
mov ax, 1000 ; Low word of dividend
mov dx, 0 ; High word of dividend
mov cx, 10 ; Divisor
div cx ; DX:AX ÷ CX → AX=100, DX=0
32-bit Division
- Dividend: EDX:EAX (64-bit, EDX = high dword, EAX = low dword)
- Divisor: 32-bit register or memory
- Quotient: EAX
- Remainder: EDX
Example (unsigned)
Dividing .
mov eax, 20 ; Low part of dividend
xor edx, edx ; High part = 0
mov ebx, 3 ; Divisor
div ebx ; EDX:EAX ÷ EBX → EAX=6, EDX=2
Example (signed)
Dividing .
mov eax, -20 ; Dividend
cdq ; Sign-extend into EDX
mov ebx, 3 ; Divisor
idiv ebx ; EDX:EAX ÷ EBX → EAX=-6, EDX=-2
64-bit Division (x86-64)
- Dividend: RDX:RAX (128-bit, RDX = high qword, RAX = low qword)
- Divisor: 64-bit register or memory
- Quotient: RAX
- Remainder: RDX
Example (unsigned)
Dividing .
mov rax, 1000 ; Low part of dividend
xor rdx, rdx ; High part = 0
mov rcx, 10 ; Divisor
div rcx ; RDX:RAX ÷ RCX → RAX=100, RDX=0
Example (signed)
Dividing .
mov rax, -20 ; Dividend
cqo ; Sign-extend into RDX
mov rcx, 3 ; Divisor
idiv rcx ; RDX:RAX ÷ RCX → RAX=-6, RDX=-2
Key Points
- Clear the high register (
dx
,edx
,rdx
) for unsigned division unless dividing a large number. - For signed division, use
cwd
(16-bit),cdq
(32-bit), orcqo
(64-bit) to sign-extend beforeidiv
. - Quotient always goes into the accumulator register (AL, AX, EAX, RAX).
- Remainder always goes into the high register (AH, DX, EDX, RDX).
Visualizing the Division Operation
Below is a plain-text “visual” of what div rsi
expects and what those three instructions do. I’ll show:
- how
RDX:RAX
forms the 128-bit dividend - a step-by-step register snapshot (before/after each instruction)
- why x86 uses
RDX:RAX
historically - what happens if
RDX
isn’t zero
1) What RDX:RAX
means (concatenation)
For 64-bit unsigned division, div r/m64
always divides the 128-bit value in RDX:RAX
by the given 64-bit operand.
High 64 bits Low 64 bits
┌──────────────────────────────────────┬──────────────────────────────────────┐
│ RDX │ RAX │
└──────────────────────────────────────┴──────────────────────────────────────┘
bits 127 … 64 bits 63 … 0
128-bit dividend = (RDX << 64) | RAX
If your dividend fits in 64 bits (your “distance”), you make the high half zero:
RDX:RAX = 0:distance
Then div rsi
computes:
Quotient = (RDX:RAX) / RSI → stored in RAX
Remainder = (RDX:RAX) % RSI → stored in RDX
2) Step-by-step with a concrete example
Assume:
distance = 1000 ; in RDI
time = 3 ; in RSI
Initial state (conceptual)
RAX = ???? RBX = ???? RCX = ???? RDX = ????
RSI = 3 RDI = 1000 RBP = ???? RSP = ????
R8 … R15 = (don’t care for this example)
After: mov rax, rdi
Copy distance → low 64 bits of the dividend.
RAX = 1000
RDX = ???? ; still unknown / old garbage
RSI = 3
RDI = 1000
After: xor rdx, rdx
Zero the high 64 bits of the dividend.
RAX = 1000
RDX = 0
RSI = 3
RDI = 1000
Now the 128-bit dividend is exactly 0x000…000:0x000…3E8
(that is, 1000
).
After: div rsi
Divide (RDX:RAX) by RSI → quotient in RAX, remainder in RDX.
Dividend = RDX:RAX = 0:1000
Divisor = RSI = 3
Quotient = 333
Remainder = 1
RAX = 333 ; speed
RDX = 1 ; remainder
RSI = 3
RDI = 1000
Diagram right before div
:
128-bit dividend (RDX:RAX)
┌──────────────────────────────────────┬──────────────────────────────────────┐
│ 0x0000000000000000 │ 0x00000000000003E8 │
└──────────────────────────────────────┴──────────────────────────────────────┘
RDX RAX
After div rsi
(rsi = 3
):
RAX (quotient) = 0x000000000000014D ; 333
RDX (remainder) = 0x0000000000000001 ; 1
3) Why specifically RDX:RAX
?
This is an architectural convention inherited from early x86:
- On 16-bit 8086,
div r/m16
uses DX:AX as the 32-bit dividend (AX = low, DX = high). - Extending to 32-bit gave EDX:EAX, and to 64-bit gave RDX:RAX.
- The “accumulator” register (AX/EAX/RAX) traditionally receives results (e.g., quotient), and the paired “high” register (DX/EDX/RDX) receives the remainder.
That’s why you don’t pass the dividend as a normal operand; it’s implicitly taken from RDX:RAX
.
4) What if RDX
isn’t zero?
If RDX
has a nonzero value, the dividend becomes much larger:
Dividend = (RDX << 64) + RAX
Two consequences:
- You’ll divide the wrong number.
- Worse,
div
can raise a DE (divide error) exception if the quotient wouldn’t fit in 64 bits (i.e., result ≥ 2⁶⁴). A nonzeroRDX
makes this far more likely.
That’s why, when your dividend fits in 64 bits, you must clear RDX
:
xor rdx, rdx
5) Minimal recipe to remember
; distance in rdi (≤ 64-bit), time in rsi (nonzero)
mov rax, rdi ; low half of dividend
xor rdx, rdx ; high half = 0
div rsi ; quotient → rax, remainder → rdx
If either operand can be negative, use the signed version:
mov rax, rdi
cqo ; sign-extend into RDX:RAX
idiv rsi ; signed divide → rax=quotient, rdx=remainder
That’s all the moving parts: the dividend is a 128-bit lane made from RDX
(high) and RAX
(low). You load the low half with your 64-bit number, force the high half to zero, then let div
produce quotient in RAX
and remainder in RDX
.
Resources
Resource | Description |
---|---|
1.2 - Modulo in x86-64 Assembly (Intel Syntax) | Next note |
Guide to Using Assembly in Visual Studio | a tutorial on building and debugging assembly code in Visual Studio |
Intel x86 Instruction Set Reference | |
Intel’s Pentium Manuals | (the full gory details) |