ASM 8086无DIV指令实现除法:如何编写b=a/6程序?
Great question! Since 6 factors into 2×3, we can split the problem into two manageable parts: dividing by 2 (which you already have down with SAR), then handling division by 3 without using the DIV instruction. Let’s cover a few practical, efficient approaches tailored for 8086 assembly.
Approach 1: Multiplicative Inverse (Fast for Unsigned Numbers)
For unsigned integers, we can leverage the multiplicative inverse trick. Dividing by 3 is equivalent to multiplying by 1/3, and in modular arithmetic (for 2^16 values), we can use an approximate inverse that works for integer division. For 16-bit unsigned numbers, the inverse of 3 is 0x5555 (since 0x5555 * 3 = 0xFFFF, which is just 1 less than 2^16).
Here’s how to combine this with dividing by 2:
; Assume 'a' is a 16-bit unsigned variable, result stored in 'b' mov ax, a ; Load a into AX shr ax, 1 ; Divide by 2 (SHR works for unsigned; SAR also works here) mov bx, 0x5555 ; Load 16-bit inverse of 3 mul bx ; Multiply AX by BX: result is in DX:AX mov ax, dx ; Take the high 16 bits (equivalent to shifting right by 16) mov b, ax ; Store the final result (a/6)
- Why this works: Multiplying by
0x5555and taking the high 16 bits effectively computes(a/2) * (2^16 / 3) / 2^16, which approximates(a/2)/3 = a/6perfectly for all unsigned 16-bit values.
Approach 2: Shift-and-Add with Adjustment (Works for Signed Numbers)
If you need to handle signed integers (negative numbers), SAR correctly divides by 2, but we need a division-by-3 method that respects sign. We can use a series of shifts and adds based on the infinite series approximation of 1/3 (1/3 = 1/4 + 1/16 + 1/64 + ...), then adjust for remainder errors.
Here’s a robust implementation:
; Assume 'a' is a 16-bit signed variable, result stored in 'b' mov ax, a ; Load a into AX sar ax, 1 ; Divide by 2 (SAR preserves sign for negative numbers) mov cx, ax ; Save a copy of (a/2) for calculations ; Approximate (a/2)/3 using shift-and-add sar ax, 2 ; ax = (a/2)/4 add ax, cx ; ax = (a/2) + (a/2)/4 = 5*(a/2)/4 sar ax, 2 ; ax = 5*(a/2)/16 add ax, cx ; ax = (a/2) + 5*(a/2)/16 = 21*(a/2)/16 sar ax, 2 ; ax = 21*(a/2)/64 add ax, cx ; ax = (a/2) + 21*(a/2)/64 = 85*(a/2)/64 ; Adjust for remainder to get exact division mov dx, ax shl dx, 1 ; dx = 2*ax add dx, ax ; dx = 3*ax (check what 3*our quotient is) sub cx, dx ; cx = (a/2) - 3*ax (remainder) ; Adjust quotient based on remainder sign and value cmp cx, 0 jge no_neg_adjust sub ax, 1 ; If remainder is negative, subtract 1 from quotient no_neg_adjust: cmp cx, 3 jl no_pos_adjust add ax, 1 ; If remainder is >=3, add 1 to quotient no_pos_adjust: mov b, ax ; Store the final signed result (a/6)
- Why this works: The shift-and-add steps get us close to the true quotient, and the remainder adjustment fixes any off-by-one errors, ensuring correct signed division.
Approach 3: Subtraction Loop (Simple but Slow)
If you’re working with small values and prioritize simplicity over speed, a subtraction loop gets the job done. We just repeatedly subtract 3 from a/2 and count how many times we can do it before going negative.
; Assume 'a' is a 16-bit variable (signed or unsigned), result stored in 'b' mov ax, a ; Load a into AX sar ax, 1 ; Divide by 2 mov cx, 0 ; Initialize quotient counter divide_by_3_loop: sub ax, 3 js end_divide ; Stop if subtraction makes AX negative inc cx jmp divide_by_3_loop end_divide: ; cx now holds the quotient (a/6) mov b, cx
- Note: For unsigned numbers, use
JBinstead ofJSto check for underflow. This method is straightforward but inefficient for large values (e.g., dividing 65535 would take 10922 iterations).
内容的提问来源于stack exchange,提问作者Roberto Meneses

