前言
实验内容来源于王爽老师著作的《汇编语言》第3版,P218,折腾了很久,才编码完成,好记性不如烂笔头,顺手记下,算是一个小小的笔记。
实验环境
win10、dosbox、masm5.00
实验内容:解决除法溢出问题
问题描述:前面讲过,div指令可以做除法。当进行8位除法的时候,用al存储结果的商,ah存储结果的余数;进行16位除法的时候,用ax存储结果的商,dx存储结果的余数。可是,现在有一个问题,如果结果的商大于al或者ax所能存储的最大值,那么将如何?
(ps:内容比较多就懒得一一描述了。)
思考
我们可以通过公式:
来解决除法溢出问题。
公式说明:
X:被除数,范围:[0,FFFFFFFF]
X:除数,范围:[0,FFFF]
H:X高16位,范围:[0,FFFF]
L:X低16位,范围:[0,FFFF]
int():描述性运算符,取商,比如,int(38/10)=3
rem():描述性运算符,取余数,比如,rem(38/10)=8
以上都是摘自课本内容,但有一个问题是65536换成十六进制就是10000,需要两个寄存器或者mul dword ptr ds:[si]找到65536。
一开始是想着通过dw配合mul来做乘法,如下代码块:
data segment
dw 65536
data ends
code segment
...
mov ax,dx
mul dword ptr ds:[0]
code ends
但很快就发现不可行,在debug执行到这一步的是,对应的汇编指令是db 66,一看就懵了,没看懂是啥意思。
要不换寄存器试一下?可这么一来好像需要俩寄存器才能存放的下65536,既然搞不清楚就debug一下呗,反正也就浪费一点时间。
子曰:说帅不过三秒就是这种情况,用了俩寄存器,结果还是不行,代码下:
code segment
...
mov dx,1 ;65536高16位
mov ax,0 ;65536低16位
mov bx,5
mul bx
code ends
想来想去,既然正着来不行,那返过来试一下应该可以吧,65536的十六进制不是10000吗?那仔细想一下,10000对应寄存器的值可以看成dx=0001和ax=0000,这么一来,不管乘法怎么算,dx的值永远是计算结果值*1,而,(ax)=0。
别说,还真凑巧了,可行!!!
代码实现
assume cs:code,ss:stack
stack segment
db 8 dup(8)
db 8 dup(8)
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,10h
mov dx,21h
mov ax,0e88eh
mov cx,6h
call divdw
mov ax,4c00h
int 21h
divdw: push ax ;将被除数低16位入栈
;如果不想用栈,那么就需要在四个寄存器中来回交换
;先计算H/N,除数为16位,被除数为32位
mov ax,dx ;(ax)=(dx)=被除数高16位
xor dx,dx ;被除数高16位位0
div cx ;(ax)=商,(dx)=余数
;int(H/N)*65536
mov bx,ax ;暂存结果高16位
;由于65536的十六进制是10000,所以做乘法的时候ax永远是0,
;(dx)=1=商*1=(ax)
;同理,[rem(H/N)*65536+L]/N 等价于 (H%N)*10000+L
;由于10000需要两个寄存器来存储(ax)=0000,(dx)=0001
;所以(H%N)*10000在寄存器中的值为(ax)=0000,(dx)=H%N*1
;同时L的取值范围[0,ffff]
;所以,(H%N*65536+L])寄存器的值为(dx)=H%N*1,(ax)=L
;(dx)=余数
pop ax ;弹出除数低16位
div cx ;[rem(H/N)*65536+L]/N
mov cx,dx ;将余数送入cx
mov dx,bx ;将结果高16位送入dx
ret
code ends
end start
debug试一了下,结果是正确的,这也算是一个取巧的方式。