【问题标题】:Calculating LCM in assembly x86在汇编 x86 中计算 LCM
【发布时间】:2020-11-11 16:16:18
【问题描述】:

我有以下汇编代码

.global _start

.section .text
_start:
    movq a, %rax
    movq b, %rbx
    imul %rbx, %rax
    cmp %rbx, %rax
    je gcd_calculated
    ja l1
    sub %rax, %rbx
    jmp _start
l1:
    sub %rbx, %rax
    jmp _start
gcd_calculated:
    div %rax
    movq %rax, (c)
    

a,b 是四边形,我需要计算它们的 lcm,我需要将结果分配给 c

我用上面的代码得到了错误的结果,我不知道为什么。

一般来说,我在 lcm = (a*b)/gcd 上进行中继,所以我将 a*b 存储在 %rax 中,然后计算将存储在 %eax 中的 gcd,最后在它们之间进行划分。

【问题讨论】:

  • 您每次通过循环计算和比较a * b > b,并且永远不会更改ab。这看起来像一个无限循环(大部分时间)。
  • 如果 a, b 是 64 位的,则 LCM 可能不适合 64 位结果。考虑以下值:a = 2^63 - 1b = 2^62,然后:gcd(a, b) = 1(互质),lcm(a, b) 是 125 位结果!

标签: assembly x86 x86-64 att


【解决方案1】:
/* x86-64 gcc / clang support the __int128 type: */

/*

#include <stdint.h>

unsigned __int128 lcm (uint64_t a, uint64_t b)
{
    uint64_t u = a, v = b;

    for (uint64_t t = 0; (t = v) != 0; u = t)
        v = u % v;

    if (u != 0) /* gcd(a, b) != (0) : */
        return (((unsigned __int128) a) * (b / u));

    return (0);
}

*/

/* x86-64 Mach-O (OSX) / ELF calling conventions:
 * first arg (a) in %rdi, second arg (b) in %rsi;
 * 128-bit integer returned in %rdx:%rax [hi:lo] */

/* no caller-reserved registers or stack are used: */

        .text
        .globl _lcm
_lcm:
        movq    %rdi, %r8
        movq    %rsi, %rdi
        movq    %r8, %rcx
L_gcd:
        testq   %rdi, %rdi
        je      L_lcm         # %rcx = gcd(a, b)
        movq    %rcx, %rax
        xorl    %edx, %edx
        movq    %rdi, %rcx
        divq    %rdi
        movq    %rdx, %rdi
        jmp     L_gcd
L_lcm:                        # lcm(0, 0) returns (0) :
        xorl    %eax, %eax
        xorl    %edx, %edx
        testq   %rcx, %rcx    # if gcd(a, b) == 0, then:
        je      L_done        # %rdx:%rax = 0:0; return.
        movq    %rsi, %rax
        xorl    %edx, %edx    # %rdx:%rax = 0:b
        divq    %rcx          # %rax = (b / g)
        mulq    %r8           # %rdx:%rax = a * (b / g)
L_done:
        ret

【讨论】:

    【解决方案2】:

    就像 1201ProgramAlarm 所说,您正在计算和比较循环的每次迭代中 ab 的原始值。如果您想遵循当前的方法,则必须在每次循环之前将这些值存储在内存中。

    我实际上要做的只是先进行 gcd 计算,然后在最后计算 (a*b)/gcd

    _start:
        movq a(%rip), %rbx
        movq b(%rip), %rcx
    gcd:
        cmp %rbx, %rcx
        je gcd_calculated
        ja l1
        sub %rcx, %rbx
        jmp gcd
    l1:
        sub %rbx, %rcx
        jmp gcd
    gcd_calculated:
        movq a(%rip), %rax
        xor %rdx, %rdx
        div %rbx
        imul b(%rip), %rax
        movq %rax, c(%rip)
    

    【讨论】:

    • 由于ab 都可以被g = gcd(a, b) 整除,因此最好计算(a / g) * b(b / g) * a - 这可以避免div 引发异常的情况,如果(a * b) / g 不适合 64 位 %rax 结果。
    • div %rbx 将 RDX:RAX 作为 128 位红利之前,我没有看到任何清除 RDX 的东西。这可能发生在静态链接的 Linux 可执行文件中,其中寄存器在进入_start 的用户空间之前被内核清零。此外,您应该像普通人一样使用a(%rip) 来访问静态存储(如果您想加载和存储,而不仅仅是使用寄存器)。
    • 另外,GCD 的重复减法?通常你将它们彼此分开,直到你得到一个 0 余数(如Greatest Common Divisor 或布雷特的回答);对于某些数字对,应该收敛得更快。 (虽然对于一些小数字,我猜这可能比微编码的 64 位 div 的启动开销更快,尤其是在 Ice Lake 之前的 Intel CPU 上)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-09
    • 2013-05-15
    • 1970-01-01
    • 2023-03-10
    • 2017-10-23
    • 1970-01-01
    相关资源
    最近更新 更多