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 位数字相乘的主要内容,如果未能解决你的问题,请参考以下文章