没有 div 的 ASM 8086 除法
Posted
技术标签:
【中文标题】没有 div 的 ASM 8086 除法【英文标题】:ASM 8086 division without div 【发布时间】:2020-05-30 15:04:16 【问题描述】:我需要在 asm 8086 中编写一个类似 b=a/6 但没有 DIV 指令的程序。我知道如何使用 SAR,但只有 2、4、8、16...
mov ax,a
sar ax,1 ;//div a by 2
mov b,ax
我的问题是如何将 div 设为 6?
【问题讨论】:
您可以使用减法并计算归零所需的次数,例如。 30/6=5 和 30-6-6-6-6-6=0 所以对于 30,你必须 5 次减去 6 才能达到零 对于一个固定的(编译时常数)除数,有一个使用乘法来进行精确整数除法的定点技巧:Why does GCC use multiplication by a strange number in implementing integer division?。另外,您要签名还是未签名的部门?div
是无符号的,sar
是有符号的(对于负数,舍入方式与 idiv
不同)
【参考方案1】:
给定another answer 的方法是简单的暴力循环,对于较大的a
值可能需要一段时间。这是一个使用较大块的版本(像长除法问题一样工作),专门编码为将有符号数除以 6:
; signed divide by 6
mov ax,a
mov cx,1000h ; initial count of how many divisors into ax to check for
mov bx,6000h ; value of "divisor * cx"
xor dx,dx ; result
top:
cmp ax,bx
jl skip
; we can fit "cx" copies of the divisor into ax, so tally them
add dx,cx
sub ax,bx
; optionally can have a "jz done" here to break out of the loop
skip:
shr bx,1
shr cx,1
jnz top
; copy result into ax
mov ax,dx
如果需要除 6 以外的东西,则需要调整初始的 cx
和 bx
值。 cx
是保留第 14 位设置的除数的 2 的幂(因为第 15 位是符号位;对于无符号除法,您希望设置第 15 位)。 bx
是 2 的幂。如果 a
的初始值有限制,您可以调整初始 cx
和 bx
值,但必须小心,因为如果让它们太小。
【讨论】:
【参考方案2】:您可以使用减法并计算归零所需的次数,例如。 30/6=5 和 30-6-6-6-6-6=0 所以对于 30,你必须 5 次减去 6 才能达到零
类似的东西:
mov cx,0
mov ax, dividend
divloop:
cmp ax, 0
jle done
sub ax, divisor
inc cx
jmp divloop
done:
;result is in cx
【讨论】:
【参考方案3】:由于您在示例中使用了 sar
指令,我假设您需要 signed 除以 6。
以下代码使用倒数常数的乘法。它将结果向下舍入为 -∞:
mov ax,0x2AAB ; round(0x10000/6)
imul A ; DX:AX contains the signed 32-bit result of the multiplication
mov B,dx ; take only the upper 16 bits
如果您需要将结果四舍五入为 0,则将负结果加一:
mov ax,0x2AAB
imul A
test dx,dx ; set FLAGS according to DX
jns skip
inc dx ; increase DX if it was negative
skip: mov B,dx
最后,如果你需要无符号除以6,你需要一个更精确的常数和一个移位:
mov ax,0xAAAB ; round(0x40000/6)
mul A ; DX:AX contains the unsigned 32-bit result of the multiplication
shr dx,1
shr dx,1 ; shift DX right by 2 (8086 can't do "shr dx,2")
mov B,dx
【讨论】:
使用test dx,dx
根据DX设置FLAGS,it's more efficient than or
在某些CPU上,而不是在8086上更糟。在现代CPU上,编译器通常会生成无分支代码,有条件地用@之类的东西加1 987654331@ / sar ax,15
/ sub dx, ax
减去 0 或 -1,但在实际的古代 CPU 上会慢得多。
这些魔法常量是 GCC 用于short
:godbolt.org/z/4baG7K。有趣的是,对于已签约的部门来说,它只是占据上半场而无需进一步转移;在其他情况下,需要轮班来处理大红利:Divide by 10 using bit shifts? 是该错误的 32 位示例,它仅适用于足够小的红利。还有Why does division by 3 require a rightshift (and other oddities) on x86?.
负数加一,也可以使用进位标志:cmp dh,0x80
/sbb dx,-1
。【参考方案4】:
小学毕业
x/6 = x * 1/6;
看看当你让 C 编译器去做时会发生什么
unsigned short fun ( unsigned short x )
return(x/6);
32 位 x86
0000000000000000 <fun>:
0: 0f b7 c7 movzwl %di,%eax
3: 69 c0 ab aa 00 00 imul $0xaaab,%eax,%eax
9: c1 e8 12 shr $0x12,%eax
c: c3 retq
32 位臂
00000000 <fun>:
0: e59f3008 ldr r3, [pc, #8] ; 10 <fun+0x10>
4: e0802093 umull r2, r0, r3, r0
8: e1a00120 lsr r0, r0, #2
c: e12fff1e bx lr
10: aaaaaaab
同样的故事。将其转换为 8086。
所以 6 = 3 * 2,所以我们确实需要除以 3。然后调整
unsigned short fun ( unsigned short x )
return(x/3);
00000000 <fun>:
0: e59f3008 ldr r3, [pc, #8] ; 10 <fun+0x10>
4: e0802093 umull r2, r0, r3, r0
8: e1a000a0 lsr r0, r0, #1
c: e12fff1e bx lr
10: aaaaaaab
少一点移位。其中一个转变是提高精度,另一个是因为其中有一个除以 2。
你当然可以做减法循环。否则它是长除法,实际上很容易编码。
【讨论】:
以上是关于没有 div 的 ASM 8086 除法的主要内容,如果未能解决你的问题,请参考以下文章