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 sizeDividend locationQuotient goes toRemainder goes to
8-bitAX (16-bit register)ALAH
16-bitDX:AX (32-bit value)AXDX
32-bitEDX:EAX (64-bit value)EAXEDX
64-bitRDX:RAX (128-bit value)RAXRDX

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 in EDX:EAX.

    • If EDX is zero, you’re really just dividing EAX by ebx.

    • 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), or cqo (64-bit) to sign-extend before idiv.
  • 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:

  1. how RDX:RAX forms the 128-bit dividend
  2. a step-by-step register snapshot (before/after each instruction)
  3. why x86 uses RDX:RAX historically
  4. 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:

  1. You’ll divide the wrong number.
  2. Worse, div can raise a DE (divide error) exception if the quotient wouldn’t fit in 64 bits (i.e., result ≥ 2⁶⁴). A nonzero RDX 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


ResourceDescription
1.2 - Modulo in x86-64 Assembly (Intel Syntax)Next note
Guide to Using Assembly in Visual Studioa tutorial on building and debugging assembly code in Visual Studio
Intel x86 Instruction Set Reference
Intel’s Pentium Manuals(the full gory details)