x86 汇编将两个 32 位数字相乘

Posted

技术标签:

【中文标题】x86 汇编将两个 32 位数字相乘【英文标题】:x86 assembly multiply two 32 bit numbers 【发布时间】:2017-05-09 00:09:36 【问题描述】:

我在空闲时间学习英特尔汇编语言(att 语法),我只是想知道如何在不使用 mul 命令的情况下将两个数字相乘,比如说 5 和 2?

【问题讨论】:

因为那里有 2 的倍数,所以可以使用左移、右移到倍数/除以 2 或 2 的倍数。 嗨 Jack,如果您想在 ASM 中获得答案,请考虑使用 assembly tag 而不是 c tag... 你也可以使用imul。或aad. 【参考方案1】:

除非您的 CPU 出现某种缺陷,否则您只需使用mul 命令:-)

但是,在一般意义上,您只需要注意乘法是重复加法,因此4 x 7 是 7 批 4 加在一起:4 + 4 + 4 + 4 + 4 + 4 + 4

所以这种野兽的简单伪代码是:

def mul(unsigned a, unsigned b):  # line 1
    res = 0                       # line 2
    while b > 0:                  # line 3
        res = res + a             # line 4
        b = b - 1                 # line 5
    return res                    # line 6

在示例试运行中使用您的测试数据显示了它是如何工作的:

Line#   a    b   res
-----  ---  ---  ---
    1    5    2    ?
    2              0
    3                 (b>0, keep going)
    4              5
    5         1
    3                 (b>0, keep going)
    4             10
    5         0
    3                 (b==0, exit loop)
    6                 (returns 10)

请注意,这仅使用无符号值,您只需稍作修改即可处理有符号值:

def mul(int a, int b):
    sign = 1
    if a < 0:
        a = -a
        sign = -sign
    if b < 0:
        b = -b
        sign = -sign

    res = 0
    while a > 0:
        res = res + b
        a = a - 1

    if sign == -1:
        res = -res
    return res 

还请记住,实际上有更有效的乘法方法涉及值的位移(最小化所需的加法),而不是简单的重复加法。

我的意思是像9999 x 9999 这样的计算将使用简单的方法执行大约 10,000 次加法。通过使用移位,您可以将其中一个数字的每个数字所需的加法限制为 9 个,而另一个数字的每个数字只需不到 1 个,这意味着您可以在上述计算中进行大约 40 个加法。

当您意识到可以将9999 x 9999 简化为:

     9999 x 9 -> nine additions
+   99990 x 9 -> nine additions
+  999900 x 9 -> nine additions
+ 9999000 x 9 -> nine additions
                 \____________/
                       |
                       V
                 three additions

如果您想更详细地了解移位的工作原理,***有一个 article on the topic。


顺便说一句,当您乘以一个常数时,您可以获得相当不错的性能,因为您事先知道需要执行哪些操作。例如,将寄存器乘以 10 可以通过以下方式完成(请记住,我的组装时间已经过去很久了):

mul_ax_by_10: push bx      ; save registers
              shl  ax      ; ax <- orig_ax * 2
              push ax      ; save for later add
              shl  ax
              shl  ax      ; ax <- orig_ax * 8
              pop  bx      ; bx <- orig_ax * 2
              add ax, bx   ; ax <- (orig_ax * 8) + (orig_ax * 2)
                           ;    <- orig_ax * (8 + 2)
                           ;    <- orig_ax * 10
              pop  bx      ; restore saved register
              ret          ; result in ax

【讨论】:

是的,我知道怎么用c代码写出来,但我不知道怎么用汇编写出来。 你能给我一个例子,说明如何使用 shift 命令将两个数字相乘。假设我们将 5 存储在 a 中,将 10 存储在 b 中,当我们将 5 和 10 转换为二进制表示时。 在 x86 上,你有神奇的 LEA 指令,将 EAX 乘以一个常数 10 很简单:leal (,%eax,4), %edx + addl %edx, %eax + addl %eax, %eax。当然,这会破坏EDX 寄存器,所以如果你想保存它,你可以进行推送和弹出操作。【参考方案2】:

由于您使用 c 标记了问题,我假设您正在为 GCC 的内联汇编器而苦苦挣扎。

在过去的小数时代,您进行了乘法运算 - 例如11 * 14 - 如下:

    你取了乘数的最右边数字 (11) = 1,并与被乘数 (14) = 14 相乘

    您将乘数左边的下一位数 = 1,乘以被乘数 = 14,然后将结果小数左移一位数 = 140。您还可以移动被乘数而不是结果:1 * 140 = 140。

    您将结果添加到最终结果中:14 + 140 = 154。

此算法在二元论的美丽新世界中也有效:

    通过将乘数向右移动 = 1,取乘数 (1011b) 的最右边 。将其“乘”以被乘数 (1110b)。这不是真正的乘法。您只有两个选项:0 * 1110b = 0 和 1 * 1110b = 1110b。结果取决于位 0 或被乘数。将其添加到最终结果中。

    如果乘数大于空值,则下一位将作为乘数中最右边的位等待。将被乘数向左移动一位(上面步骤 2 中的第二个选项)并转到步骤 1。

GCC 计划:

#include <stdio.h>

unsigned mul (unsigned multiplier, unsigned multiplicand)

    unsigned r = 0;
    while (multiplier)
    
        if (multiplier & 1) r += multiplicand;
        multiplier >>= 1;
        multiplicand <<= 1;
    
    return r;


unsigned mul_asm (unsigned multiplier, unsigned multiplicand)

    unsigned result = 0;

    asm
    (
        "movl %[multiplier], %%edx;"        // EDX = multiplier
        "movl %[multiplicand], %%ecx;"      // ECX = multiplicand
        "xorl %%eax, %%eax;"                // Result = 0

        "L1:;"                              // While-loop
        "shrl $1, %%edx;"                   // Get rightmost bit of the multiplier
        "jnc 1f;"                           // Skip the next line if this bit == 0
        "leal (%%ecx,%%eax), %%eax;"        // Add multiplicand to result (without changing flags)
        "1:;"                               // Local jump label
        "leal (,%%ecx,2), %%ecx;"           // Shift multiplicand left by one bit (without changing flags)
        "jnz L1;"                           // While EDX != 0 (zero flag from the shrl-line)

        "movl %%eax, %[result];"            // Return value

        : [result] "=m" (result)            // Output
        : [multiplier] "m" (multiplier),    // Input
          [multiplicand] "m" (multiplicand)
        : "%eax", "%ecx", "%edx", "cc"      // Clobbered registers & flags
    );

    return result;


int main ( void )

    unsigned result, multiplier, multiplicand;

    multiplier = 17;
    multiplicand = 23;

    result = multiplier * multiplicand;
    printf ("direct:  %u * %u = %u\n", multiplier, multiplicand, result);

    result = mul (multiplier,multiplicand);
    printf ("mul:     %u * %u = %u\n", multiplier, multiplicand, result);

    result = mul_asm (multiplier,multiplicand);
    printf ("mul_asm: %u * %u = %u\n", multiplier, multiplicand, result);

    return 0;

【讨论】:

以上是关于x86 汇编将两个 32 位数字相乘的主要内容,如果未能解决你的问题,请参考以下文章

在 8086 微处理器上将 32 位两个数字相乘

80X86汇编 mul 乘法指令

如何在 MIPS 中将两个数字相乘,得到大于 32 位的乘积?

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

将两个长数相乘

如何将两个数组与每个元素相乘作为数字c ++