最近在学习《王爽汇编》,其中一个作业是编写子程序实现除法溢出问题。
描述如下:汇编语言中div可以实现除法,当进行8位除法(被除数16位,除数8位,结果8位)时,商存在al中,余数存在ah中;进行16位除法(被除数32位,除数16位,结果16位,商16位)时,商存在ax中,余数存在dx中。
假设一个16位除法
10000h/1
显然,结果是10000h。可系统默认的16位除法结果是16位的,但在这里10000h显然是溢出的(65536>65535),无法放到一个16位数中。改进策略是拓展结果的长度,也就是当被除数32位,除数16位,令结果为32位,余数是16位。
这里需要用到一个公式:
X/N = int(H/N)*65536 + [rem(H/N)*65536+L]/N -----------------------公式①
X:被除数
N:除数
H:被除数X的高16位
L:被除数X的低16位
int()取商
rem()取余数
给出示例参数:
(ax)寄存器储存被除数的低16位 mov ax,4240h
(dx)寄存器储存被除数的高16位 mov dx,000fh
(cx)寄存器储存16位的除数 mov cx,000ah除法结果的高16位储存在(dx)中,低16位储存在(ax)中,余数储存在(cx)中
自然语言表示就是求0f4240h/000ah的商和余数
思路1(比较巧妙)
仔细观察公式①的第一项:
int(H/N)*65536
int(H/N):被除数的高16位/除数,这里将H拓展为一个32位的数(0000xxxxh),由于其高16位全为0,所以H/N的商一定不会溢出(超过16位)。
那乘以65536应该怎么做呢?我们只需要记得将H/N的商放到一个代表高16位的寄存器中就可以了,并不需要真的乘65536(事实上*10000h也无法简单地用16位乘法实现)。
再看公式②的第二项:
[rem(H/N)*65536+L]/N
rem(H/N)已经在第一项除法中得到了,假设 X = rem(H/N),接下来实现(X+L)/N,显然也是一个32位数/16位数的除法。但这里存在一个问题:如何保证除法的商不会溢出呢?
在这里简单的证明一下**(不想看可以跳过直接看代码)**:
因为 X 是 H/N 的余数,所以 X<=N-1 (X不可能大于N),L是被除数的低16位,不妨令 X=N-1,L=FFFFh。只需证明
[(N-1)|FFFFh]/N<10000h
(((N-1)|FFFFh)以N-1为高16位,以FFFF为低16位。比如N-1 = FFFE,则分子为FFFEFFFFh)。
原式=[(N-1)*65536 + 65535]/N=65536 - 1/N < 10000h
接下来是代码实现(附注释)
assume ds:datasg,ss:stacksg,cs:codesg
datasg segment
datasg ends
stacksg segment
dw 0,0,0,0,0,0,0,0
stacksg ends
codesg segment
start:mov ax,stacksg
mov ss,ax
mov sp,16
mov ax,4240h
mov dx,000Fh
mov cx,0Ah
call divdw
mov ax,4c00h
int 21h
;参考公式X/N=int(H/N)*65536+[rem(H/N)*65536+L]/N
;可以把该32位/16位的除法视作2个伪16除法,高位(dx)中存的是0
;首先000Fh/0Ah,