Post

10. Multiplication and Division Instructions

10. Multiplication and Division Instructions

Multiplication and division in x86-64 assembly have some unique characteristics that differ from other arithmetic operations. They use specific registers and have special behaviors that are important to understand.

A taste or introduction to these operations I’ve already shown you here

Multiplication Instructions

x86-64 provides different multiplication instructions for various data sizes.

1. MUL - Unsigned Multiplication

Syntax: MUL operand

How it works:

  • Single operand - the other operand is implicit:

    • 8-bit: AL × operand → result in AX
    • 16-bit: AX × operand → result in DX:AX
    • 32-bit: EAX × operand → result in EDX:EAX
    • 64-bit: RAX × operand → result in RDX:RAX

Flags affected: CF and OF (set if result exceeds destination size)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
; 8-bit multiplication
mov al, 10
mov bl, 5
mul bl              ; AX = AL × BL = 10 × 5 = 50

; 16-bit multiplication  
mov ax, 1000
mov bx, 200
mul bx              ; DX:AX = AX × BX = 1000 × 200 = 200,000

; 32-bit multiplication
mov eax, 50000
mov ebx, 300
mul ebx             ; EDX:EAX = EAX × EBX = 15,000,000

; 64-bit multiplication
mov rax, 1000000
mov rbx, 500
mul rbx             ; RDX:RAX = RAX × RBX = 500,000,000

When you see notation like DX:AX, it means two 16-bit registers combined to form one 32-bit value.

DX:AX = (DX × 65536) + AX

  • DX contains the high 16 bits (most significant part)
  • AX contains the low 16 bits (least significant part)
  • Together they form a 32-bit number

Visual Representation:

DX:AX = 32-bit value
┌───────────────┬───────────────┐
│      DX       │       AX      │
│   (High 16)   │   (Low 16)    │
└───────────────┴───────────────┘

Following example will help you in understanding it. Run this instructions in rappel

1
2
3
4
5
mov ax, 0x1234     ; AX = 0x1234
mov dx, 0x5678     ; DX = 0x5678

; DX:AX combined = 0x56781234
; How we calculate: (0x5678 × 65536) + 0x1234 = 0x56780000 + 0x1234 = 0x56781234

Following example will help you in clearing why we use “Register Pairs” while doing multiplication.

1
2
3
4
5
6
7
8
; 16-bit multiplication example
mov ax, 1000       ; 16-bit number
mov bx, 1000       ; 16-bit number  
mul bx             ; 1000 × 1000 = 1,000,000

; Where does 1,000,000 go?
; - It's too big for AX (max 65,535)
; - Solution: Use DX:AX together

When you run this in rappel or in SASM you’ll find that both rax and rdx are updated.

1000 is 0x3E8 in hexadecimal, and 1000 × 1000 (i.e., 0x3E8 × 0x3E8) equals 0xF4240. However, since we used 16-bit multiplication with BX, and AX/BX/CX/DX can only hold 16 bits each, the result is split across two registers. The higher 16 bits (0x000F) are stored in DX and the lower 16 bits (0x4240) are stored in AX, forming the complete 32-bit result DX:AX = 0x000F:0x4240.

> mul bx
rax=0000000000004240 rbx=00000000000003e8 rcx=0000000000000000
rdx=000000000000000f rsi=0000000000000000 rdi=0000000000000000

All Register Pairs in x86-64

Operation SizeDividend/ProductQuotient/Remainder
8-bitAXAL = quotient, AH = remainder
16-bitDX:AXAX = quotient, DX = remainder
32-bitEDX:EAXEAX = quotient, EDX = remainder
64-bitRDX:RAXRAX = quotient, RDX = remainder

2. IMUL - Signed Multiplication

IMUL has three different forms, offering flexibility for signed multiplication operations:

Form 1: Single Operand (Same as MUL but Signed)

Syntax: IMUL operand

It works exactly similar to MUL but with signed numbers.

Single operand - the other operand is implicit:

  • 8-bit: AL × operand → result in AX
  • 16-bit: AX × operand → result in DX:AX
  • 32-bit: EAX × operand → result in EDX:EAX
  • 64-bit: RAX × operand → result in RDX:RAX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
; 8-bit signed multiplication
mov al, -5
mov bl, 4
imul bl             ; AX = AL × BL = -5 × 4 = -20

; 16-bit signed multiplication  
mov ax, -1000
mov bx, 200
imul bx             ; DX:AX = AX × BX = -1000 × 200 = -200,000

; 32-bit signed multiplication
mov eax, -50000
mov ebx, 300
imul ebx            ; EDX:EAX = EAX × EBX = -15,000,000
Form 2: Two Operands

Syntax: IMUL dest, src

How it works:

  • dest = dest × src
  • Result fits entirely in dest register
  • More convenient for most cases where overflow isn’t expected
1
2
3
4
5
6
7
8
9
10
11
12
; 32-bit signed multiplication
mov eax, 25
mov ebx, -4
imul eax, ebx       ; EAX = 25 × -4 = -100

; 64-bit signed multiplication
mov rcx, 100
imul rcx, -50       ; RCX = 100 × -50 = -5000

; 16-bit signed multiplication
mov ax, -10
imul ax, 20         ; AX = -10 × 20 = -200
Form 3: Three Operands

Syntax: IMUL dest, src1, src2

How it works:

  • dest = src1 × src2
  • Most flexible form
  • Result stored in dest register
  • Neither source operand is modified
1
2
3
4
5
; Various sized signed multiplications
imul rax, rbx, -10      ; RAX = RBX × -10
imul ecx, edx, 5        ; ECX = EDX × 5
imul ax, bx, -3         ; AX = BX × -3
imul r8, r9, 100        ; R8 = R9 × 100

Temperature Conversion

1
2
3
4
5
6
; Convert Celsius to Fahrenheit: F = (C × 9/5) + 32
mov eax, 20           ; Celsius temperature
imul eax, 9           ; EAX = 20 × 9 = 180
mov ebx, 5
idiv ebx              ; EAX = 180 ÷ 5 = 36
add eax, 32           ; EAX = 36 + 32 = 68°F

Division Instructions

Division in x86-64 works with register pairs, similar to multiplication, but in reverse. While multiplication combines smaller inputs into larger outputs, division splits larger inputs into smaller outputs (quotient and remainder).

1. DIV - Unsigned Division

Syntax: DIV operand

How it works:

  • Dividend is implicit in specific register pairs
  • Divisor is the operand
  • Result: Quotient and remainder are stored in specific registers
Data SizeDividendDivisorQuotientRemainder
8-bitAXoperandALAH
16-bitDX:AXoperandAXDX
32-bitEDX:EAXoperandEAXEDX
64-bitRDX:RAXoperandRAXRDX

Flags affected: None (but division by zero causes exception)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
; 8-bit division
mov ax, 50         ; Dividend = 50
mov bl, 7          ; Divisor = 7
div bl             ; AL = 7 (quotient), AH = 1 (remainder)

; 16-bit division
mov dx, 0          ; Clear upper part
mov ax, 1000       ; Dividend = 1000
mov bx, 300        ; Divisor = 300
div bx             ; AX = 3 (quotient), DX = 100 (remainder)

; 32-bit division
mov edx, 0         ; Clear upper part
mov eax, 100000    ; Dividend = 100,000
mov ebx, 30000     ; Divisor = 30,000
div ebx            ; EAX = 3 (quotient), EDX = 10,000 (remainder)
Practical Division Examples

Example 1: 16-bit Division

1
2
3
4
5
6
7
8
9
; Divide 100,000 by 300
mov dx, 1          ; High part = 1
mov ax, 34464      ; Low part = 34,464  
mov bx, 300        ; Divisor
div bx             ; Divide DX:AX by BX

; Result:
; - AX = quotient = 100,000 ÷ 300 = 333
; - DX = remainder = 100,000 mod 300 = 100

Let’s verify this calculation:

  • Dividend: DX:AX = 1:34464 = (1 × 65536) + 34464 = 100,000
  • Division: 100,000 ÷ 300 = 333 with remainder 100
  • Quotient: 333 → stored in AX
  • Remainder: 100 → stored in DX

In rappel you can verify this -

> div bx             ; Divide DX:AX by BX
rax=000000000000014d rbx=000000000000012c rcx=0000000000000000
rdx=0000000000000064 rsi=0000000000000000 rdi=0000000000000000

Example 2: 32-bit Division

1
2
3
4
5
6
7
8
9
; Divide 15,000,000 by 30,000
mov edx, 0         ; High part = 0
mov eax, 15000000  ; Low part = 15,000,000
mov ebx, 30000     ; Divisor
div ebx            ; Divide EDX:EAX by EBX

; Result:
; - EAX = quotient = 15,000,000 ÷ 30,000 = 500
; - EDX = remainder = 15,000,000 mod 30,000 = 0

Division requires register pairs because we often need to divide numbers that are larger than a single register can hold:. Following example will help in understanding this -

1
2
3
4
5
6
7
8
; Problem: Divide 100,000 by 300
; - 100,000 is too big for AX alone (max 65,535)
; - Solution: Use DX:AX as a 32-bit dividend

mov dx, 1          ; High 16 bits of 100,000
mov ax, 34464      ; Low 16 bits of 100,000  
mov bx, 300
div bx             ; Now we can divide 100,000 by 300

When you run division in rappel or SASM, you’ll see both the quotient and remainder registers updated:

> div bx             ; Now we can divide 100,000 by 300
rax=000000000000014d rbx=000000000000012c rcx=0000000000000000
rdx=0000000000000064 rsi=0000000000000000 rdi=0000000000000000
rip=0000000000400004 rsp=00007ffdcbdd42b0 rbp=0000000000000000

Interpretation: RAX = 0x014D = 333 (quotient) RDX = 0x0064 = 100 (remainder) RBX = 0x012C = 300 (divisor)

2. IDIV - Signed Division

IDIV works exactly like DIV but handles signed numbers:

1
2
3
4
5
6
7
8
9
; Signed division examples
mov ax, -50        ; Dividend = -50
mov bl, 7          ; Divisor = 7
idiv bl            ; AL = -7 (quotient), AH = -1 (remainder)

mov eax, -100000
cdq                ; Sign-extend EAX into EDX:EAX
mov ebx, 30000
idiv ebx           ; EAX = -3 (quotient), EDX = -10,000 (remainder)

The cdq instruction is an x86 assembly instruction that converts a signed 32-bit integer in the EAX register into a 64-bit signed integer in the EDX:EAX register pair. It performs this by sign-extending the EAX register: the sign bit (the highest bit) of EAX is copied into all the bits of the EDX register. This is crucial for division operations, where it prepares the dividend from a 32-bit value to be divided by another 32-bit value.

Other instructions which are similar to this are -

CWD The 16-bit version of cdq, which converts a signed word in AX to a signed doubleword in DX:AX.

CQO The 64-bit version, which converts a signed quadword in RAX to a signed 128-bit integer in RDX:RAX

You can always check this amazing site felixcloutier.

Things to be kept in mind -

For unsigned division: Clear the upper register

1
2
3
4
5
6
7
mov dx, 0          ; Clear DX for 16-bit division
mov ax, 1000
div bx

mov edx, 0         ; Clear EDX for 32-bit division  
mov eax, 100000
div ebx

For signed division: Use sign extension

1
2
3
4
5
6
7
mov ax, -1000
cwd                ; Sign-extend AX into DX:AX for 16-bit
idiv bx

mov eax, -100000
cdq                ; Sign-extend EAX into EDX:EAX for 32-bit
idiv ebx

Next Up: We’ll explore Shift and Rotate Instructions - powerful bit manipulation tools that are faster than multiplication/division for powers of two!

This post is licensed under CC BY 4.0 by the author.