【问题标题】:How can I multiply two 64-bit numbers using x86 assembly language?如何使用 x86 汇编语言将两个 64 位数字相乘?
【发布时间】:2008-09-17 21:17:59
【问题描述】:

我该怎么办...

  • 两个 64 位数字相乘

  • 两个 16 位十六进制数相乘

...使用汇编语言。

我只能使用寄存器 %eax、%ebx、%ecx、%edx 和堆栈。

编辑:哦,我在 x86 上使用 ATT 语法
EDIT2:不允许反编译成程序集...

【问题讨论】:

标签: assembly x86 bigint extended-precision


【解决方案1】:

使用可能是您的课程教科书,Randall Hyde 的“汇编语言的艺术”。

4.2.4 - Extended Precision Multiplication

虽然 8x8、16x16 或 32x32 乘法通常就足够了,但有时您可能希望将更大的值相乘。您将使用 x86 单操作数 MUL 和 IMUL 指令进行扩展精度乘法..

在执行扩展精度乘法时要记住的最重要的事情可能是您还必须同时执行多精度加法。将所有部分产品相加需要多次添加才能产生结果。下面的清单演示了在 32 位处理器上乘以两个 64 位值的正确方法..

(有关完整的组装清单和插图,请参阅链接。)

【讨论】:

    【解决方案2】:

    如果这是 64x86,

    function(x, y, *lower, *higher)
    movq %rx,%rax     #Store x into %rax
    mulq %y           #multiplies %y to %rax
    #mulq stores high and low values into rax and rdx.
    movq %rax,(%r8)   #Move low into &lower
    movq %rdx,(%r9)   #Move high answer into &higher
    

    【讨论】:

    • 问题是关于 x86,而不是 x86-64,这显然太容易了,因为不需要做任何特别的事情
    【解决方案3】:

    由于您使用的是 x86,因此您需要 4 条 mull 指令。将 64 位量分成两个 32 位字,并将低位字乘以结果的最低和第二低位字,然后是来自不同数字的两对低位和高位字(它们转到结果的第二位和第三位最低字)和最后将两个高单词变成结果的2个最高单词。将它们加在一起不要忘记处理携带。您没有指定输入和输出的内存布局,因此无法编写示例代码。

    【讨论】:

    • 我认为 high*high 部分是不需要的,因为无论如何它都会溢出
    • @BCS:正确,只有当您想要 64x64 => 128 位全乘 (example with SSE2) 时才需要高 x 高部分。 h x h 的两半与 128 位全乘积的前两个块对齐,完全在输入宽度结果之外。 godbolt.org/z/GWT86f78c 显示 GCC -O3 -m32 输出用于 32 位 x86 上的非扩展 uint64_t 产品,使用两个非扩展 32 位 imul 用于叉积,一个扩展 mul 用于低 x 低,无需 adc。
    【解决方案4】:

    此代码假定您需要 x86(不是 x64 代码),您可能只需要 64 位产品,并且您不关心溢出或有符号数字。 (签名版本类似)。

    MUL64_MEMORY:
         mov edi, val1high
         mov esi, val1low
         mov ecx, val2high
         mov ebx, val2low
    MUL64_EDIESI_ECXEBX:
         mov eax, edi
         mul ebx
         xch eax, ebx  ; partial product top 32 bits
         mul esi
         xch esi, eax ; partial product lower 32 bits
         add ebx, edx
         mul ecx
         add ebx, eax  ; final upper 32 bits
    ; answer here in EBX:ESI
    

    这不符合 OP 的确切寄存器约束,但结果完全符合 x86 提供的寄存器。 (这段代码未经测试,但我认为它是正确的)。

    [注意:我从另一个已关闭的问题中转移了(我的)这个答案,因为这里没有其他“答案”直接回答了这个问题]。

    【讨论】:

    • @PeterCordes:你确定吗?低 32 位的“进位”是“add ebx edx”指令中 edx 中的 32 位。
    • 哦对了,我们只做 64x64 => 64 位,结果的低 32 位完全由lo1 * lo2 => 64 位的低半部分决定,没有额外的可以带入lo1 * hi2 => 32 位和lo2 * hi1 => 32 位的高半结果。所以最后两个可以用 2 操作数 imul ecx, esi / imul edi, ebx 而不是 xchgmul 来完成,因为你只想要一个 64 位的结果,而不是 96 或 128。
    【解决方案5】:

    这取决于您使用的语言。从我学习 MIPS 汇编的记忆中,有一个 Move From High 命令和一个 Move From Lo 命令,或者 mflo 和 mfhi。 mfhi 存储总数的前 64 位,而 mflo 存储总数的低 64 位。

    【讨论】:

    • OP 说它是 x86,而不是 MIPS
    【解决方案6】:

    啊组装,我已经有一段时间没有使用它了。所以我假设这里真正的问题是你正在使用的微控制器(我曾经在汇编中编写代码)没有 64 位寄存器?如果是这种情况,你将把你正在处理的数字分开,并对这些数字进行多次乘法。

    从你的措辞来看,这听起来像是一项家庭作业,所以我不会再详细说明了:P

    【讨论】:

      【解决方案7】:

      只需进行正常的长乘法,就好像您将一对 2 位数字相乘,除了每个“数字”实际上是一个 32 位整数。如果您将地址 X 和 Y 处的两个数字相乘并将结果存储在 Z 中,那么您想要做的(在伪代码中)是:

      Z[0..3] = X[0..3] * Y[0..3]
      Z[4..7] = X[0..3] * Y[4..7] + X[4..7] * Y[0..3]

      请注意,我们将丢弃结果的高 64 位(因为 64 位数字乘以 64 位数字就是 128 位数字)。另请注意,这是假设小端。此外,请注意有符号乘法与无符号乘法。

      【讨论】:

      • 第一部分缺少高位
      • 等等,你把我弄糊涂了——你说要去掉结果的高 64 位?为什么会是……嗯,理性的……?
      【解决方案8】:

      找一个支持 64 位的 C 编译器(GCC 做 IIRC)编译一个程序,然后得到反汇编。 GCC 可以自行将其吐出,您可以使用正确的工具将其从目标文件中取出。

      他们是 x86 上的 32bX32b = 64b 操作

      a:b * c:d = e:f
      // goes to
      e:f = b*d;
      x:y = a*d;  e += x;
      x:y = b*c;  e += x;
      

      其他的都溢出了

      (未经测试)

      编辑仅无符号

      【讨论】:

        【解决方案9】:

        我打赌你是一名学生,所以看看你是否可以完成这项工作:逐字逐句,并使用位移。想出最有效的解决方案。当心符号位。

        【讨论】:

        • 最后,位移不会比乘法指令更有效——至少在 x86 上不会,在 32 位值上不会。不过,要了解处理器执行乘法操作的基础知识,这是一个很好的练习。
        【解决方案10】:

        如果你想要 128 模式试试这个...

        __uint128_t AES::XMULTX(__uint128_t TA,__uint128_t TB)
        {
            union
            {
                __uint128_t WHOLE;
                struct
                {
                    unsigned long long int LWORDS[2];
                } SPLIT;
            } KEY;
            register unsigned long long int __XRBX,__XRCX,__XRSI,__XRDI;
            __uint128_t RESULT;
        
            KEY.WHOLE=TA;
            __XRSI=KEY.SPLIT.LWORDS[0];
            __XRDI=KEY.SPLIT.LWORDS[1];
            KEY.WHOLE=TB;
            __XRBX=KEY.SPLIT.LWORDS[0];
            __XRCX=KEY.SPLIT.LWORDS[1];
            __asm__ __volatile__(
                         "movq          %0,             %%rsi           \n\t"       
                         "movq          %1,             %%rdi           \n\t"
                         "movq          %2,             %%rbx           \n\t"
                         "movq          %3,             %%rcx           \n\t"
                         "movq          %%rdi,          %%rax           \n\t"
                         "mulq          %%rbx                           \n\t"
                         "xchgq         %%rbx,          %%rax           \n\t"
                         "mulq          %%rsi                           \n\t"
                         "xchgq         %%rax,          %%rsi           \n\t"
                         "addq          %%rdx,          %%rbx           \n\t"
                         "mulq          %%rcx                           \n\t"
                         "addq          %%rax,          %%rbx           \n\t"
                         "movq          %%rsi,          %0              \n\t"
                         "movq          %%rbx,          %1              \n\t"
                         : "=m" (__XRSI), "=m" (__XRBX)
                         : "m" (__XRSI),  "m" (__XRDI), "m" (__XRBX), "m" (__XRCX)
                         : "rax","rbx","rcx","rdx","rsi","rdi"
                         );
            KEY.SPLIT.LWORDS[0]=__XRSI;
            KEY.SPLIT.LWORDS[1]=__XRBX;
            RESULT=KEY.WHOLE;
            return RESULT;
        }
        

        【讨论】:

        • 为什么需要输入和输出在内存中,而不是让 gcc 在必要时加载它们?只使用__uint128_t 并让 gcc 来做,你会得到更好的 asm。此外,这几乎只是复制了 Ira Baxter 的答案。如果它是good inline asm,它可能是一个有用的补充,但它不是。使用大量固定寄存器,并且需要内存操作数远非最佳。您应该只让 gcc 处理除 64x64 -> 128 mul 指令之外的所有内容,使用具有 2 个输入和 2 个输出的两个单独的 asm 语句。这不应该是volatile
        • 因为我将 C 连接到 Assembler 并且我没有 GCC 使用的库的源代码,并且我不想使用置换,因为这会搞砸我正在寻找的答案对于,我还想加快一段代码,提高 n^p mod n OK。
        • 您应该使用类似asm ("mulq %[src]" : "=a"(lo64_result), "=d"(hi64_result) : "a"(KEY.SPLIT.LWORDS[1]), [src] "rm" (KEY.SPLIT.LWORDS[0])); 的代码,然后使用第二个类似的 asm 语句进行第二次乘法运算。 gcc 将执行所有mov 指令。您只需告诉它需要在哪里进行操作,以及结果将出现在哪里。 gcc 库代码的源与任何内容无关,内存位移也无关。请参阅x86 标签 wiki 以获取有关如何编写不可怕的内联 asm 的链接。编译:goo.gl/izSfMi
        • 我实际上是从 Borland Turbo C++ 中提取的,所以我没有使用 debug86.exe 进行复制
        【解决方案11】:

        如果你想要 128 位乘法,那么这应该是 AT&T 格式的。

        __uint128_t FASTMUL128(const __uint128_t TA,const __uint128_t TB)
        {
            union
            {
                __uint128_t WHOLE;
                struct
                {
                    unsigned long long int LWORDS[2];
                } SPLIT;
            } KEY;
            register unsigned long long int __RAX,__RDX,__RSI,__RDI;
            __uint128_t RESULT;
        
        KEY.WHOLE=TA;
        __RAX=KEY.SPLIT.LWORDS[0];
        __RDX=KEY.SPLIT.LWORDS[1];
        KEY.WHOLE=TB;
        __RSI=KEY.SPLIT.LWORDS[0];
        __RDI=KEY.SPLIT.LWORDS[1];
        __asm__ __volatile__(
            "movq           %0,                             %%rax                   \n\t"
            "movq           %1,                             %%rdx                   \n\t"
            "movq           %2,                             %%rsi                   \n\t"
            "movq           %3,                             %%rdi                   \n\t"
            "movq           %%rsi,                          %%rbx                   \n\t"
            "movq           %%rdi,                          %%rcx                   \n\t"
            "movq           %%rax,                          %%rsi                   \n\t"
            "movq           %%rdx,                          %%rdi                   \n\t"
            "xorq           %%rax,                          %%rax                   \n\t"
            "xorq           %%rdx,                          %%rdx                   \n\t"
            "movq           %%rdi,                          %%rax                   \n\t"
            "mulq           %%rbx                                                   \n\t"
            "xchgq          %%rbx,                          %%rax                   \n\t"
            "mulq           %%rsi                                                   \n\t"
            "xchgq          %%rax,                          %%rsi                   \n\t"
            "addq           %%rdx,                          %%rbx                   \n\t"
            "mulq           %%rcx                                                   \n\t"
            "addq           %%rax,                          %%rbx                   \n\t"
            "movq           %%rsi,                          %%rax                   \n\t"
            "movq           %%rbx,                          %%rdx                   \n\t"
            "movq           %%rax,                          %0                      \n\t"
            "movq           %%rdx,                          %1                      \n\t"
            "movq           %%rsi,                          %2                      \n\t"
            "movq           %%rdi,                          %3                      \n\t"
            : "=m"(__RAX),"=m"(__RDX),"=m"(__RSI),"=m"(__RDI)
            :  "m"(__RAX), "m"(__RDX), "m"(__RSI), "m"(__RDI)
            : "rax","rbx","ecx","rdx","rsi","rdi"
        );
        KEY.SPLIT.LWORDS[0]=__RAX;
        KEY.SPLIT.LWORDS[1]=__RDX;
        RESULT=KEY.WHOLE;
        return RESULT;
        }
        

        【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-07-05
        • 1970-01-01
        • 1970-01-01
        • 2014-11-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多