在 8086 微处理器上将 32 位两个数字相乘
Posted
技术标签:
【中文标题】在 8086 微处理器上将 32 位两个数字相乘【英文标题】:Multiplying 32 bit two numbers on 8086 microprocessor 【发布时间】:2015-03-25 03:20:34 【问题描述】:我有代码示例,用于在 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
。
多用两个寄存器(我不知道应该用哪个寄存器)来保存第二个和第三个16位乘法(因为乘法将是64位。16位4次。我目前有DX和CX。)
将循环编号更新为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
我的问题是:
-
如何为一个 32 位数字处理两个不同的寄存器。 (寄存器是 16 位的,所以我必须将数字分成两个寄存器)
我可以为此目的使用哪些寄存器?我可以随意使用any注册吗?
提前致谢。
【问题讨论】:
我猜这是一个家庭作业式的问题,否则你可能会重用一些已经编写和测试过的代码。如果是作业,请查看Stack Overflow: How can I multiply and divide using only bit shifting and adding? 并尝试弄清楚如果您是原始 ZX Spectrum ROM 的作者,您将如何在Z80 处理器上实现“大数字”。结合。完成 将两个 32 位数字相乘,您需要 64 位目标来存储结果。它是四个 16 位寄存器。考虑将结果存储在内存中 我不明白它与 Z80 和您提供的 *** 链接有什么关系。 *** 链接没有提供有关我的任何问题的任何信息。 @xmojmr 这是我已经在我的问题中输入的内容。我对算法没有问题,我对编码有问题。我不知道如何使用(以及哪些寄存器)将 32 位两个数字相乘。 @AlexanderZhak 您是否需要按位执行此操作,还是允许您使用mul
?
【参考方案1】:
给男人一条鱼,然后等等等等……
很好,你有一个代码示例。但是你了解算法吗?
好的,让我们通过一个简化的例子一步一步来:将AL
和AH
中的两个8位寄存器相乘,并将结果存储在DX
中。
顺便说一句,你可以使用任何你喜欢的寄存器,除非这个或那个指令需要任何特定的寄存器。例如,SHL reg, CL
。
但在我们真正开始之前,我们对您提供的算法进行了一些优化。组装就是优化,你知道的。无论是速度还是尺寸。否则你在 C# 或 smth 中做 bloatware。否则。
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:
这是Long Multiplication algorithm
12h = 00010010
x
34h = 01110100
--------
00000000
01110100
00000000
00000000
01110100
00000000
00000000
00000000
将 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
干杯 ;)
【讨论】:
顺便说一句,这是右移方法是一种反向实现。通常人们会向左移动 只是为了好玩,我对此进行了测试(在一个 64 位 Linux 程序中,该程序根据 64 位imul
检查结果并退出 0 或 1。pastebin.com/pjfwFYna)。我不必更改任何代码,因为您不使用任何 16 位寻址模式或任何东西。它工作正常,所以 IDK 发布另一个答案的用户正在谈论什么。 (如果他们甚至在谈论你的。)顺便说一句,在英特尔 Sandybridge 等现代 CPU 上,RCR reg, 1
比SHR reg, 1
慢。我猜你说的是实际的 8086。我认为这比 4x MUL + 一些 ADC 甚至在 8086 上都慢,不过......【参考方案2】:
解决方案 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;
【讨论】:
@Alexander 的答案对我有用,他的测试输入(结果 =0xb00ea4e242d2080
)。请注意,他在dx:cx:bx:ax
中生成结果,这与您更明智地选择BX:CX:DX:AX
的顺序不同。他的代码在 64 位模式下组装相同,所以我只是将 shl edx, 16
/ mov dx, cx
/ ... / shl rdx, 16
/ mov dx, ax
放在循环之后,并使用 2 个负载 + imul rax, rcx
来检查结果64 位硬件乘法。
顺便说一句,32x32 => 64b 使用 16x16 => 32b mul
应该只需要 4 mul
指令(和一些 ADC)。这是 32 位模式的 64x64 => 128b 示例(使用 HLA 语法或其他东西,并且可能使用比溢出/重新加载更多的 MOV。plantation-productions.com/Webster/www.artofasm.com/Linux/HTML/…)。
感谢您的阅读,对不起,我说过,我认为您的算法只能有一个可以替换的移位指令(ShR RCR),但我错了。我没有测试过你的解决方案,我很快就阅读了它。我从没想过只添加 32 位因子,因为它必须转换为 64 位变量。它非常快,并且使用最佳系统将因子向右移动而不是向左移动!【参考方案3】:
据记录,8086 有一个mul
instruction,这使得这变得更容易(并且在具有快速mul
的后来的 CPU 上更有效)。在最初的 8086 上它真的很慢,但运行RCL 多精度移位循环 32 次在所有 CPU 上都吸了很多!这个版本有更少的静态代码,这很好。
您只需要三个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
的内存,作为局部变量或函数参数或标签。或者其中一些可能位于此函数不使用的寄存器中,如果您更改语法使其不是寻址模式。
;; 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 => 64-bit multiply in 32-bit mode 是相同的问题,操作数大小不同,从寄存器中的所有 4 个值开始,使用 xchg
管理事物。)
请注意,我们不保存lo * hi
产品的高半 DX 结果,因为我们只需要 32 位结果,而不是完整的 32x32 => 64 位结果。这些产品的低 16 位添加到我们最终 32 位产品的上半部分。 (而且我们不需要将它们进位到 64 位结果的最高 16 位字中,因此我们可以在最后一个 mul 之前添加它们。)
adc
如何传播结果
multiply two 32-bit numbers to get a 64-bit number, on a 8086 (32x32 => 64-bit with 16-bit multiplies) 如果您确实需要高半部分。
Multiplying 64-bit number by a 32-bit number in 8086 asm 显示更大的情况,这些部分如何适合总和。
16 * 32 => 32 位乘法会更容易,只需两个 mul
和一个 add
(加上一堆 mov
以将数据放入正确的位置)。例如,请参阅执行此操作的阶乘循环:multiply two consecutive times in assembly language program(该答案还显示了扩展精度乘法数学的工作原理,就像您为纸和铅笔算法添加术语以对多个十进制数字进行乘法一样。)
【讨论】:
以上是关于在 8086 微处理器上将 32 位两个数字相乘的主要内容,如果未能解决你的问题,请参考以下文章
如何在 MIPS 中将两个数字相乘,得到大于 32 位的乘积?