【问题标题】:multiply two 32-bit numbers to get a 64-bit number, on a 8086 (32x32 => 64-bit with 16-bit multiplies)在 8086 上将两个 32 位数字相乘得到一个 64 位数字(32x32 => 64 位乘以 16 位)
【发布时间】:2012-11-07 05:55:11
【问题描述】:

如何在汇编中将两个 32 位数字相乘或一个 32 位另一个 16 位相乘,有人知道算法吗?

data1 dw 32bit
data2 dw 32bit    
mov ax,data2
Mul data1

【问题讨论】:

标签: assembly x86-16 multiplication bigint


【解决方案1】:

首先,dw 用于创建一个 16 位(“字”)值。它不会保存 32 位值。您需要使用 dd 来存储 32 位“dword”,或使用一对 16 位值。

要乘以一对 32 位值,结果可以是 64 位(例如 0xFFFFFFFF * 0xFFFFFFFF = 0xFFFFFFFE00000001)。对于 8086(而不仅仅是 80386 或更高版本的实模式代码),有一条 MUL 指令,但它仅限于乘以 2 个 16 位值(并获得 32 位结果)。这意味着您希望将每个 32 位值视为一对 16 位值。

如果A被拆分为A_low(前32位数字的最低16位)和A_high(前32位数字的最高16位),B被拆分为B_low和B_high同样的方法;那么:

  A * B = A_low * B_low
          + ( A_high * B_low ) << 16
          + ( A_low * B_high ) << 16
          + ( A_high * B_high ) << 32

代码可能如下所示(NASM 语法):

         section .data
first:   dw 0x5678, 0x1234  ;0x12345678
second:  dw 0xDEF0, 0x9ABC  ;0x9ABCDEF0
result:  dw 0, 0, 0, 0      ;0x0000000000000000
         section .text

    mov ax,[first]          ;ax = A_low
    mul word [second]       ;dx:ax = A_low * B_low
    mov [result],ax
    mov [result+2],dx       ;Result = A_low * B_low

    mov ax,[first+2]        ;ax = A_high
    mul word [second]       ;dx:ax = A_high * B_low
    add [result+2],ax
    adc [result+4],dx       ;Result = A_low * B_low
                                     ; + (A_high * B_low) << 16

    mov ax,[first]          ;ax = A_low
    mul word [second+2]     ;dx:ax = A_low * B_high
    add [result+2],ax
    adc [result+4],dx       ;Result = A_low * B_low
                                     ; + (A_high * B_low) << 16
                                     ; + (A_low * B_high) << 16
    adc word [result+6], 0   ; carry could propagate into the top chunk

    mov ax,[first+2]        ;ax = A_high
    mul word [second+2]     ;dx:ax = A_high * B_high
    add [result+4],ax
    adc [result+6],dx       ;Result = A_low * B_low
                                     ; + (A_high * B_low) << 16
                                     ; + (A_low * B_high) << 16
                                     ; + (A_high * B_high) << 32

我们不需要在第二步([first+2] * [second])之后需要adc word [result+6], 0,因为它的高半部分最多为0xfffe[result+4] 那时已经为零(因为此代码只工作一次),因此 adc [result+4],dx 无法包装并产生执行。最多可以产生0xffff

(可以使用 adc dx, 0 / mov [result+4], dx 来避免依赖于 result 的那部分已经被归零。类似地,adc 到归零寄存器中可以用于第一次写入 [result+6] , 使此代码无需先归零即可使用 result。)


如果您实际使用的是 80386 或更高版本,则要简单得多:

         section .data
first:   dd 0x12345678
second:  dd 0x9ABCDEF0
result:  dd 0, 0            ;0x0000000000000000
         section .text

    mov eax,[first]          ;eax = A
    mul dword [second]       ;edx:eax = A * B
    mov [result],eax
    mov [result+4],edx       ;Result = A_low * B_low

【讨论】:

  • 谢谢布伦丹,这对我帮助很大。
  • 我知道这是旧的,但你错过了 8086 版本中的一个进位。在第 2 步和第 3 步之后应该有一个ADC [result+6],00h
  • 这当然可以优化为在寄存器中完成更多add/adc 工作。使用 memory-destination add 和 adc 只对最清楚地显示数学有意义,而不是在实际代码中实际应该做什么。 (但作为这个问题的答案,+1)
  • @AhmedAeonAxan:很好发现。但只有在第 3 步之后:查看我的编辑。我们可以证明进位不可能在第 2 步之后传播那么远,因为0xffff ^ 2 = 0xfffe0001 所以高半部分最多是0xfffe + CF
  • 我还在 32-bit extended multiplication via stack 上发布了我自己的优化版本(以及 64x64 => 32 位模式的 128 位版本,对两个产品使用 SSE2 pmuludq 看看是否这是值得的,并且有助于缓解注册压力。)
猜你喜欢
  • 1970-01-01
  • 2019-08-24
  • 1970-01-01
  • 2010-11-24
  • 1970-01-01
  • 1970-01-01
  • 2012-03-16
  • 2015-10-17
  • 1970-01-01
相关资源
最近更新 更多