1

我正在编写一个函数库来为有符号整数类型s0128, s0256,和浮点类型, , s0512,提供所有常规运算符和函数。s1024f0128f0256f0512f1024

我现在正在编写s0128, s0256, s0512,s1024乘法例程,但得到的错误结果让我感到困惑。我假设我可以用 64 位imul rcx指令级联乘法(在 中产生 128 位结果rdx:rax),就像我可以用指令对无符号操作数做同样的事情mul rcx......但答案imul是错误的。

我怀疑有一些技巧可以使这项工作,也许是混合imulmul说明 - 或其他东西。或者是否有某种原因不能用有符号乘法指令实现更大的乘法?


因此,您了解该技术,我将描述s0128操作数的最小版本。

           arg2.1   arg2.0  : two 64-bit parts of s0128 operand
           arg1.1   arg1.0  : two 64-bit parts of s0128 operand
           ---------------
       0  out.edx  out.eax  : output of arg1.0 * arg2.0
 out.edx  out.eax           : output of arg1.0 * arg2.1
 -------------------------
 out.2    out.1    out.0    : sum the above intermediate results
 out.edx  out.eax           : output of arg1.1 * arg2.0
 -------------------------
 out.2    out.1    out.0    : sum the above intermediate results

每次代码将两个 64 位值相乘时,都会在edx:eax. 每次代码生成一个 128 位结果时,它会将结果相加到一个 64 位寄存器的累加三元组中,其中包含addq, adcq,adcq指令(其中最后一条adcq指令仅加零以确保传播任何进位标志)。

s0128当我将小负数乘以小正数作为测试时,结果为负数,但在 128 位结果中的高 64 位值的底部有一个或两个非零位。这对我来说意味着在多精度有符号乘法中的传播有些不太正确。

当然,级联对于s0256, s0512,来说要广泛得多s1024

我错过了什么?我必须将两个操作数都转换为无符号,执行无符号乘法,然后如果一个(但不是两个)操作数为负,则否定结果?或者我可以使用带imul符号的乘法指令计算多精度结果吗?

4

1 回答 1

4

当您从较小的乘法中构建扩展精度的有符号乘法时,您最终会得到有符号和无符号算术的混合。

特别是,如果将有符号值分成两半,则将上半部分视为有符号,而将下半部分视为无符号。事实上,扩展精度加法也是如此。

考虑这个任意示例,其中AHAL代表 的高半部分和低半部分ABHBL代表 的高半部分和低半部分B。(注意:这些并不意味着代表 x86 寄存器的一半,只是被乘数的一半。)这些L术语是无符号的,并且这些H术语是有符号的。

              AH : AL
           x  BH : BL
  -------------------
              AL * BL    unsigned x unsigned => zero extend to full precision
         AH * BL           signed x unsigned => sign extend to full precision
         AL * BH         unsigned x   signed => sign extend to full precision
    AH * BH                signed x   signed

AL * BL产品未签名,因为 AL 和 BL 都未签名。因此,当您将其提升到结果的完全精度时,它会得到零扩展。

和产品混合有符号AL * BHAH * BL无符号值。生成的产品已签名,当您将其提升到结果的完全精确度时,需要对其进行签名扩展。

以下 C 代码演示了以 16×16 乘法实现的 32×32 乘法。同样的原则也适用于从 64×64 乘法构建 128×128 乘法。

#include <stdint.h>
#include <stdio.h>

int64_t mul32x32( int32_t x, int32_t y )
{
    int16_t x_hi = 0xFFFF & (x >> 16);
    int16_t y_hi = 0xFFFF & (y >> 16);

    uint16_t x_lo = x & 0xFFFF;
    uint16_t y_lo = y & 0xFFFF;


    uint32_t lo_lo = (uint32_t)x_lo * y_lo;    // unsigned x unsigned
    int32_t  lo_hi = (x_lo * (int32_t)y_hi);   // unsigned x   signed
    int32_t  hi_lo = ((int32_t)x_hi * y_lo);   //   signed x unsigned
    int32_t  hi_hi = ((int32_t)x_hi * y_hi);   //   signed x   signed


    int64_t  prod = lo_lo 
                  + (((int64_t)lo_hi + hi_lo) << 16) 
                  + ((int64_t)hi_hi << 32);

    return prod;
}

int check(int a, int b)
{
    int64_t ref = (int64_t)a * (int64_t)b;
    int64_t tst = mul32x32(a, b);

    if (ref != tst)
    {
        printf("%.8X x %.8X => %.16llX vs %.16llX\n",
                (unsigned int)a,         (unsigned int)b, 
                (unsigned long long)ref, (unsigned long long)tst);
        return 1;
    }

    return 0;
}


int main()
{
    int a = (int)0xABCDEF01;
    int b = (int)0x12345678;
    int c = (int)0x1234EF01;
    int d = (int)0xABCD5678;

    int fail = 0;

    fail += check(a, a);
    fail += check(a, b);
    fail += check(a, c);
    fail += check(a, d);

    fail += check(b, b);
    fail += check(b, c);
    fail += check(b, d);

    fail += check(c, c);
    fail += check(c, d);

    fail += check(d, d);

    printf("%d tests failed\n", fail);
    return 0;
}

即使您将被乘数分成两个以上的部分,这种模式也会扩展。也就是说,只有签名数字中最重要的部分被视为已签名。所有其他作品均未签名。考虑这个例子,它将每个被乘数分成 3 部分:

                      A2 : A1 : A0
                   x  B2 : B1 : B0
  ---------------------------------
                           A0 * B0    => unsigned x unsigned   => zero extend
                      A1 * B0         => unsigned x unsigned   => zero extend
                 A2 * B0              =>   signed x unsigned   => sign extend
                      A0 * B1         => unsigned x unsigned   => zero extend
                 A1 * B1              => unsigned x unsigned   => zero extend
            A2 * B1                   =>   signed x unsigned   => sign extend
                 A0 * B2              => unsigned x   signed   => sign extend
            A1 * B2                   => unsigned x   signed   => sign extend
       A2 * B2                        =>   signed x   signed

由于所有混合符号和符号扩展的乐趣,将有符号×有符号乘法实现为无符号×无符号乘法通常更容易,如果被乘数不同,则在最后有条件地取反。(事实上​​,当你得到扩展精度浮点数时,只要你保持像 IEEE-754 这样的符号大小形式,你就不必处理有符号乘法。)

这个汇编 gem展示了如何有效地否定扩展精度值。(宝石页面有点过时,但您可能会发现它很有趣/有用。)

于 2013-12-25T18:17:12.670 回答