[英]Multiplying 32 bit two numbers on 8086 microprocessor
我有代码示例,用于在 8086 上将两个 16 位数字相乘并尝试将其更新为两个 32 位数字相乘。
start:
MOV AX,0002h ; 16 bit multiplicand
MOV BX,0008h ; 16 bit multiplier
MOV DX,0000h ; high 16 bits of multiplication
MOV CX,0000h ; low 16 bits of multiplication
MOV SI,10h ; loop for 16 times
LOOP:
MOV DI,AX
AND DI,01h
XOR DI,01h
JZ ADD
CONT:
RCR DX,1
RCR CX,1
SHR AX,1
DEC SI
CMP SI,0
JNZ LOOP
JMP END ; ignore here, it's not about multiplication.
ADD:
ADD DX,BX
JMP CONT
上面的代码语句将两个 16 位数字相乘。
要将其更新为 32 位两个数字,我知道我需要更新,例如:
AX
更改为00000002h
并将BX
更改为00000008h
。20h
(在这种情况下为SI
)(对于 32 位编号,这是 32 次)8086 是 16 位微处理器,所以它的寄存器也是。 我无法为寄存器分配 32 位长的数字。
8086的寄存器:
REG: AX, BX, CX, DX, AH, AL, BL, BH, CH, CL, DH, DL, DI, SI, BP, SP.
SREG: DS, ES, SS, and only as second operand: CS.
资料来源: http : //www.electronics.dit.ie/staff/tscarff/8086_instruction_set/8086_instruction_set.html
我的问题是:
提前致谢。
给一个人一条鱼,等等等等……
很好,你有一个代码示例。 但是你懂算法吗?
好的,让我们通过一个简化的例子一步一步地完成它:将AL
和AH
两个 8 位寄存器相乘,并将结果存储在DX
。
顺便说一句,你可以使用任何你喜欢的寄存器,除非这个或那个指令需要任何特定的寄存器。 例如, SHL reg, CL
。
但在我们真正开始之前,您提供的算法有一些优化。 你知道,组装就是优化。 无论是速度还是尺寸。 否则你会在 C# 或 smth 中做膨胀软件。 别的。
MOV DI,AX
AND DI,01h
XOR DI,01h
JZ ADD
这部分所做的只是检查AX
的第一位(位 #0)是否已设置。 你可以简单地做
TEST AX, 1
JNZ ADD
但是您只需要测试一位,因此TEST AL, 1
而不是TEST AX, 1
可以为您节省一个字节。
下一个,
RCR DX,1
不需要轮换,所以它可以简单地为SHR DX, 1
。 但是这两条指令的执行时间相同,并且都是两个字节长,因此在本例中无关紧要。
下一个,
DEC SI
CMP SI,0
JNZ LOOP
永远不要在DEC
之后与零进行比较。 是动静! 简单地做
DEC SI
JNZ LOOP
接下来,不必要的循环拆分
JZ ADD
CONT:
. . .
JMP END
ADD:
ADD DX, BX
JMP CONT
END:
. . .
应该
JNZ CONT
ADD DX, BX
CONT:
. . .
END:
. . .
在这里,我们使用了一些优化的例程:
LOOP:
TEST AL, 1
JZ SHORT CONT
ADD DX, BX
CONT:
RCR DX, 1
RCR CX, 1
SHR AX, 1
DEC SI
JNZ LOOP
END:
就是这样。 现在回到(或向前?)这段代码实际上做了什么。 以下代码示例完全模仿您的示例,但适用于 8 位寄存器。
MOV AL,12h ; 8 bit multiplicand
MOV AH,34h ; 8 bit multiplier
XOR DX, DX ; result
MOV CX, 8 ; loop for 8 times
LOOP:
TEST AL, 1
JZ SHORT CONT
ADD DH, AH
CONT:
SHR DX, 1
SHR AL, 1
DEC CX
JNZ LOOP
END:
这是一个长乘法算法
12h = 00010010
x
34h = 01110100
--------
00000000
01110100
00000000
00000000
01110100
00000000
00000000
00000000
将 shift 34h 添加两次:
0000000011101000
+
0000011101000000
----------------
0000011110101000 = 03A8
就是这样! 现在要使用更多数字,您可以使用相同的方法。 下面是 fasm 语法的实现。 结果存储在DX:CX:BX:AX
Num1 dd 0x12345678
Num2 dd 0x9abcdef0
mov si, word [Num1]
mov di, word [Num1 + 2]
xor ax, ax
xor bx, bx
xor cx, cx
xor dx, dx
mov bp, 32
_loop:
test si, 1
jz short _cont
add cx, word [Num2]
adc dx, word [Num2 + 2]
_cont:
rcr dx, 1
rcr cx, 1
rcr bx, 1
rcr ax, 1
rcr di, 1
rcr si, 1
dec bp
jnz short _loop
干杯;)
解决方案 n. 如果产品大于 32 位,则 2 似乎不起作用。 此外,移位指令是错误的。 此解决方案正常工作:
Procedure _PosLongIMul2; Assembler;
{INPUT:
DX:AX-> First factor (destroyed).
BX:CX-> Second factor (destroyed).
OUTPUT:
BX:CX:DX:AX-> Multiplication result.
TEMP:
BP, Di, Si}
Asm
Jmp @Go
@VR:DD 0 {COPY of RESULT (LOW)}
DD 0 {COPY of RESULT (HIGH)}
@Go:Push BP
Mov BP,20H {32 Bit Op.}
XOr DI,DI {COPY of first op. (LOW)}
XOr SI,SI {COPY of first op. (HIGH)}
Mov [CS:OffSet @VR ],Word(0)
Mov [CS:OffSet @VR+2],Word(0)
Mov [CS:OffSet @VR+4],Word(0)
Mov [CS:OffSet @VR+6],Word(0)
@01:ShR BX,1
RCR CX,1
JAE @00
Add [CS:OffSet @VR ],AX
AdC [CS:OffSet @VR+2],DX
AdC [CS:OffSet @VR+4],DI
AdC [CS:OffSet @VR+6],SI
@00:ShL AX,1
RCL DX,1
RCL DI,1
RCL SI,1
Dec BP
JNE @01
Mov AX,[CS:OffSet @VR]
Mov DX,[CS:OffSet @VR+2]
Mov CX,[CS:OffSet @VR+4]
Mov BX,[CS:OffSet @VR+6]
Pop BP
End;
这适用于两个无符号整数。
如果要将 32 位无符号整数乘以 16 位无符号整数,可以使用 Mul 指令如下:
Function Mul32Bit(M1:LongInt;M2:Word):LongInt; Assembler;
Asm
LEA SI,M1
Mov AX,[SS:SI]
Mov CX,[SS:SI+2]
{CX:AX contains number to multiply by}
Mov BX,M2
{BX contains number that multiply}
Mul BX
XChG AX,CX
Mov SI,DX
Mul BX
Add AX,SI
AdC DX,0
{DX:AX:CX contains the result of multiplication}
Mov DX,AX
Mov AX,CX
{DX:AX contains the partial result of m. and is the function's result}
End;
作为记录,8086 有一个mul
指令,使这更容易(并且在具有快速mul
后来的 CPU 上更有效)。 在最初的 8086 上它真的很慢,但是在所有 CPU 上运行 32 次 RCL 多精度移位循环会很糟糕! 这个版本有更少的静态代码大小,这很好。
您只需要三个mul
指令即可获得low*low
、 low*high
和high*low
产品。 (如果你想要完整的 64 位结果,另一个high*high
产品)。
8086 缺少高效的imul reg, reg
形式,不需要 DX:AX 作为隐式输出,并且不会浪费时间将高半部分放在任何地方。 所以不幸的是,与编译器在 32 位模式下的 64x64 => 64 乘法相比,我们需要更多的寄存器改组,但除此之外,这是完全相同的问题。 (见https://godbolt.org/z/ozSkt_ )
x_lo
、 x_hi
、 y_lo
和y_hi
可以是相对于bp
作为y_hi
或函数参数或标签的内存。 或者其中一些可能位于此函数不使用的寄存器中,如果您更改语法使它们不是寻址模式。
;; untested
;; inputs: uint32_t x, y in memory
;; clobbers: CX, SI, DI
mov ax, [y_lo]
mov cx, ax
mul word ptr [x_hi]
mov si, ax ; save y_lo * x_hi
mov ax, [x_lo]
mov di, ax
mul word ptr [y_hi]
add si, ax ; sum of the cross products
mov ax, di
mul cx ; DX:AX = y_lo * x_lo
add dx, si ; add the cross products into the high half
;; Result: uint32_t DX:AX = X * Y
要使用更少的 tmp 寄存器,您只需从内存中重新加载 x_lo 和 y_lo 两次,而不是将它们保存在 DI 和 CX 中。
(相关: 64x64 => 32 位模式下的 64 位乘法,这与不同操作数大小的问题相同,从寄存器中的所有 4 个值开始,并使用xchg
来管理事物。)
请注意,我们不保存lo * hi
乘积的上半部分 DX 结果,因为我们只想要 32 位结果,而不是完整的 32x32 => 64 位结果。 这些产品的低 16 位添加到我们最终的 32 位产品的上半部分。 (而且我们不需要从它们进位到 64 位结果的最高 16 位字,所以我们可以在最后一个 mul 之前添加它们。)
adc
如何传播结果 16 * 32 => 32 位乘法会更容易,只需两个mul
和一个add
(加上一堆mov
以将数据放入正确的位置)。 例如,请参阅执行此操作的阶乘循环: 在汇编语言程序中连续两次相乘(该答案还显示了扩展精度乘法数学的工作原理,与您为纸笔算法添加项以对多个数字进行乘法的方式相同十进制数字。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.