在 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】:

给男人一条鱼,然后等等等等……

很好,你有一个代码示例。但是你了解算法吗?

好的,让我们通过一个简化的例子一步一步来:将ALAH中的两个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, 1SHR 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*lowlow*highhigh*low 产品。 (如果您想要完整的 64 位结果,high*high 产品的另一个结果)。

8086 缺少高效的imul reg, reg 形式,它不需要 DX:AX 作为隐式输出,并且不会浪费时间将高半部分放在任何地方。所以不幸的是,我们需要比编译器在 32 位模式下进行 64x64 => 64 乘法更多的寄存器改组,否则这就是完全相同的问题。 (见https://godbolt.org/z/ozSkt_)

x_lox_hiy_loy_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 之前添加它们。)

Assembly multiplication of 16-bit x 32-bit => 48-bit 展示了 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 位的乘积?

将两个32位数相乘并将64位结果打印为十进制NASM程序集

求两个大整数相乘的积,数字长度在127个字符之内。

用 SSE 在 C++ 中将两个 32 位整数向量相乘的最快方法

将两个长数相乘

C:在处理 32 位程序时,在 Windows 64 位机器上将 int 转换为 int* 时出现警告