如何在汇编中将两个十六进制 128 位数字相乘
Posted
技术标签:
【中文标题】如何在汇编中将两个十六进制 128 位数字相乘【英文标题】:How can I multiply two hex 128 bit numbers in assembly 【发布时间】:2017-04-09 07:04:23 【问题描述】:我在内存中有两个 128 位的十六进制数,例如(小端序):
x:0x12 0x45 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
y:0x36 0xa1 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
我必须在这两个数字之间执行无符号乘法,所以我的新数字将是:
z:0xcc 0xe3 0x7e 0x2b 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
现在,我知道我可以将 x 和 y 的一半移动到 rax
和 rbx
寄存器中,例如,执行 mul
操作,并对另一半执行相同的操作。问题是这样做我失去了结转,我不知道如何避免这种情况。我面临这个问题大约有 4 个小时,我能看到的唯一解决方案是二进制转换 (and
shl,1
)。
你能就这个问题给我一些意见吗? 我认为最好的解决方案是按标准时间计算一个字节。
【问题讨论】:
x86 提供 64 × 64 → 128 乘法,结果在 rdx:rax 中。您可以使用该操作来获得所需的结果。如果您有兴趣,我可以提供详细信息。请告诉我你想要有符号还是无符号乘法。 很高兴。乘法必须是无符号的。但我必须做一个 128 x 128 -> 128 乘法。如果它溢出,我只保留最低部分(这是正常的)并设置 OF 标志。告诉我 简单:最大数为(2^128)-1 @Davide 你对我的回答满意吗?您还需要其他信息吗? @AdrianColomichi 这里的数字太短了,帮不上忙,简单的方法已经只有 3 个乘法 【参考方案1】:令μ = 264,那么我们可以将你的128位数字a和b分解成a = a1μ + a2 和 b = b1μ + b2。然后我们可以通过首先计算部分积来计算 c = ab 与 64 · 64 → 128 位乘法:
q1μ + q2 = a2 b2r1μ + r2 = a1b2s1μ + s2 = a2b em>1t1μ + t2 = a1b1
然后将它们累积成 256 位结果(在进行加法时注意溢出!):
c = t1μ3 + (t 2 + s1 + r1) μ2 + (s2 + r2 + q1) μ + q2
【讨论】:
只是一个观察:如果你使用他的x
,y
,z
,OP 可能更容易更好地联系起来
如果只需要结果的低128,则不需要某些项,也不需要首先累加完整的256b结果。进位严格从右到左传播,因此在您想要的 128 以上的位上发生的任何事情都不会影响结果。 (我相信你知道这一点,但自 not everyone does 以来值得指出)。因此,OP 甚至不需要计算高半部分的 a1b1 乘法。【参考方案2】:
像往常一样,询问编译器如何高效地做某事:64 位平台上的 GNU C 支持 __int128_t
和 __uint128_t
。
__uint128_t mul128(__uint128_t a, __uint128_t b) return a*b;
编译为 (gcc6.2 -O3
on Godbolt)
imul rsi, rdx # tmp94, b
mov rax, rdi # tmp93, a
imul rcx, rdi # tmp95, a
mul rdx # b
add rcx, rsi # tmp96, tmp94
add rdx, rcx #, tmp96
ret
由于这是针对 x86-64 System V 调用约定,a
在 RSI:RDI 中,而 b
在 RCX:RDX 中。 结果在 RDX:RAX 中返回。
非常漂亮,它只需要一条 MOV 指令,因为 gcc 不需要 a_upper * b_lower 的高半结果,反之亦然。它可以使用更快的 2 操作数形式的 IMUL 破坏输入的高半部分,因为它们只使用一次。
使用-march=haswell
启用BMI2,gcc 使用MULX 来避免甚至是一个MOV。
有时编译器输出并不完美,但通常通用策略是手动优化的良好起点。
当然,如果您真正首先想要的是 C 语言中的 128 位乘法,只需使用编译器的内置支持即可。这让优化器完成了它的工作,通常比在 inline-asm 中编写几个部分时提供更好的结果。 (https://gcc.gnu.org/wiki/DontUseInlineAsm)。
【讨论】:
在这个例子中,rdx 包含一半的 128 位数字,而 rdi 包含另一半? @Davide:为您突出显示返回结果位置。 128b 结果在 RDX 中有高半部分,在 RAX 中有低半部分。请注意,结果的低 64 位仅取决于两个输入的低 64 位。 (由 MUL 制作)。 对我不起作用。我想乘以 596a*18c2。然后在 rsi 我移动 59h 和 rdi 6ah。在 rcx 中我移动 18h 和 rdx 0c2h。结果在 rdx 0x4d62 和 rax 0x5054 中,与实际乘法不对应,结果必须为 0x8a5b254 @Davide:您没有将输入分成两半,而是在 64 位边界上拆分它们。您乘以0x000000590000006a * 0x00000018000000c2
,然后显然查看了结果的低 64 位。结果是0x00004d6200005054
,此代码返回为RDX = 00004d62
,以及RAX = 0x00005054
,这就是你得到的。
@Davide:显然你不明白。您需要设置 rsi=0
:rdi=0x596a
和 rcx=0
:rdx=0x18c2
来传递您描述的输入,并获得您想要的结果。此外,如果您知道高半部分为零,只需使用一条 MUL 指令即可获得 128b 的结果。以上是关于如何在汇编中将两个十六进制 128 位数字相乘的主要内容,如果未能解决你的问题,请参考以下文章
如何在 MIPS 中将两个数字相乘,得到大于 32 位的乘积?