【问题标题】:implement 64-bit arithmetic on a 32-bit machine在 32 位机器上实现 64 位算法
【发布时间】:2012-07-25 17:08:00
【问题描述】:

以下代码计算 x 和 y 的乘积并将结果存储在内存中。数据 ll_t 类型被定义为等价于 long long。

typedef long long ll_t;
void store_prod(ll_t *dest, int x, ll_t y) {
    *dest = x*y;
}

gcc 生成以下实现计算的汇编代码: dest 在 %ebp+8,x 在 %ebp+12,y 在 %ebp+16

1 movl 16(%ebp), %esi
2 movl 12(%ebp), %eax
3 movl %eax, %edx
4 sarl $31, %edx
5 movl 20(%ebp), %ecx
6 imull %eax, %ecx
7 movl %edx, %ebx
8 imull %esi, %ebx
9 addl %ebx, %ecx
10 mull %esi
11 leal (%ecx,%edx), %edx
12 movl 8(%ebp), %ecx
13 movl %eax, (%ecx)
14 movl %edx, 4(%ecx)

此代码使用三个乘法来实现多精度算术 需要在 32 位机器上实现 64 位算法。描述 用于计算产品的算法,并注释汇编代码以显示 它是如何实现你的算法的。

我看不懂上面汇编代码中的第 8 行和第 9 行。谁能帮忙?

【问题讨论】:

  • 似乎它正在做一些非常棘手的事情,不需要 add-with-carry 指令......
  • @Mysticial 没什么棘手的。在计算截断乘积时不需要 ADC,就像这里一样。
  • 标准 SO 响应:您尝试过什么?从您下面的评论中,您真正要问的是:算法是什么。好吧,那是你的功课;这就是你应该弄清楚的。到目前为止,您在试图弄清楚这一点时做了什么?你说你知道这些说明是做什么的……然后也许先把它写下来。

标签: c assembly x86


【解决方案1】:

我已将其转换为 intel 语法。

mov esi, y_low
mov eax, x
mov edx, eax
sar edx, 31
mov ecx, y_high

imul ecx, eax ; ecx = y_high *{signed} x

mov ebx, edx

imul ebx, esi ; ebx = sign_extension(x) *{signed} y_low

add ecx, ebx ; ecx = y_high *{signed} x_low + x_high *{signed} y_low

mul esi ; edx:eax = x_low *{unsigned} y_low

lea edx, [ecx + edx] ; edx = high(x_low *{unsigned} y_low + y_high *{signed} x_low + x_high *{signed} y_low)

mov ecx, dest
mov [ecx], eax
mov [ecx + 4], edx

上面的代码所做的是将 2 个 64 位有符号整数相乘,保持乘积的最低 64 位。

其他 64 位被乘数从何而来? x 符号扩展 从 32 位到 64 位。sar 指令用于将 x's 符号位复制到 edx 的所有位中。我称这个值只包含x's 符号x_highx_lowx 的值,实际上是传入例程的。

y_lowy_highy 中最不重要和最重要的部分,就像 x's x_lowx_high 一样。

从这里开始很容易:

产品 = y *{签名} x =
(y_high * 232 + y_low) *{签名} (x_high * 232 + x_low) =
y_high * {签名} x_high * 264 +
y_high *{签名} x_low * 232 +
y_low *{签名} x_high * 232 +
y_low *{签名} x_low

y_high *{signed} x_high * 264 未计算,因为它不影响乘积的最低有效 64 位。如果我们对完整的 128 位产品(挑剔的完整 96 位产品)感兴趣,我们会计算它。

y_low *{signed} x_low 使用无符号乘法计算。这样做是合法的,因为 2 的补码有符号乘法给出与无符号乘法相同的最低有效位。示例:
-1 *{签名} -1 = 1
0xFFFFFFFFFFFFFFFF *{unsigned} 0xFFFFFFFFFFFFFFFF = 0xFFFFFFFFFFFFFFFE0000000000000001(64个最低有效位相当于1)

【讨论】:

  • 很好的答案,但对于homework 标签来说可能太详细了——从 TA 的角度来看。
  • 非常感谢,我明白了。我认为的算法是 x * (y_high * 2^32 + y_low)(x is 32 bit ),这就是为什么我对第 8 行和第 9 行感到困惑。我想知道是否有必要将 x 扩展到 64 位。
  • 我明白了为什么要将 x 扩展到 64 位。
  • cdq 等价于 mov edx, eax / sar edx,31 - 它将 EAX 符号扩展为 EDX:EAX。 lea edx, [ecx + edx] 也可能是 add edx, ecx
【解决方案2】:

考虑第 8 行和第 9 行的上下文。

此时,ESI 包含y 的下半部分,EBX 包含sgn(x)。所以第 8 行只是计算 sgn(x) * (y % 2^32) 并将其存储在 EBX 中。

第 9 行借鉴了该结果。到第 9 行发生时,ECX 包含乘法的部分上半部分,即x * (y >> 32) 有符号。所以EBX+ECX 最终是我们在最后一步计算的加上我们在前一行找到的部分上半部分。

完整的算法本身非常简洁;)

编辑:回应下面的评论...

第 4 行:考虑 SAR EDX, 31(或者,如果您愿意,sar $31, %edx)的真正含义。由于EDX 是一个 32 位寄存器,因此您最终会得到两个值之一。哪两个?考虑一下它们在有符号算术上下文中的含义。

第 7 行:EDX 此时包含一些对以下操作非常有用的内容。我只是把它移到需要去的地方。

【讨论】:

  • 谢谢,但我还是没明白。为什么要将 sgn(x) * (y % 2^32) 添加到 ECX?我认为 imull %eax %ecx 已经完成了乘法的上半部分。
  • 好问题。没错,IMUL %eax, %ecx 已经处理了乘法的上半部分,但 sgn(x) * (y % 2^32) 根本没有触及上半部分。考虑sgn(x)foo % 2^32 的数学界限。您可能还认为两次操作 IMUL 会将其结果截断为 32 位,但我不认为这与相关步骤直接相关。
  • sgn(x) * (y % 2^32) 根本不触及上半部分,而是将其值添加到存储乘法上半部分的 ecx 中。
  • 这绝对是真的;我们在算法后面需要组合值,所以此时,我们使用 ECX 作为中间值,本质上是因为它就在那里,我们不再需要原始上半部分。
  • @atomicinf 我相信你的意思是“双操作数”而不是“双操作”
【解决方案3】:

imul 所做的是将 eax 的内容与 ecx 相乘,并将低 32 位保存在 eax 中,将高 32 位保存在 edx 中。

addl 据我所知,添加了两个寄存器并将其保存在第一个寄存器中,因此在本例中为 ebx。 (我不确定它是否还有其他作用,addl 后面的 l 代表 long)

【讨论】:

  • 右操作数是目的地,所以第 9 行将 %ebx + %ecx 放入 %ecx。
  • 这个link 提供了深入的知识,并认为这是一项家庭作业。
  • 该文档与 gnu 的汇编语言不匹配...见 stackoverflow.com/questions/2397528/…
  • 感谢您的回答。此代码来自书 cs_app 第二版(作业 3.55)。我知道那些汇编指令是什么意思,但我对算法感到困惑。第 4、7、8、9 行是干什么用的?
  • 双操作数 IMUL 符号乘以两个操作数并将结果存储在目标操作数中。您刚刚描述了单操作数IMUL ECX
猜你喜欢
  • 2016-01-02
  • 2011-08-08
  • 2011-04-09
  • 2016-10-28
  • 1970-01-01
  • 1970-01-01
  • 2016-07-03
  • 1970-01-01
  • 2015-04-02
相关资源
最近更新 更多