1

s0128符号付き整数型、s0256s0512s1024および浮動小数点型f0128f0256f0512のすべての従来の演算子と関数を提供する関数ライブラリを作成していますf1024

s0128, s0256, s0512,乗算ルーチンを書いてs1024いますが、間違った結果が得られて混乱しています。命令で符号なしオペランドを使用して同じことを行うことができるのと同じ方法で、64 ビットimul rcx命令 (で 128 ビットの結果を生成する) で乗算をカスケードできると仮定しましたが、 の答えは間違っています。rdx:raxmul rcximul

imulこれを機能させるには、おそらくミックスとmul指示など、いくつかのトリックがあると思います。それとも、符号付き乗算命令でより大きな乗算を実装できない理由がありますか?


テクニックを理解したので、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

コードが 2 つの 64 ビット値を乗算するたびに、128 ビットの結果が に生成されedx:eaxます。コードが 128 ビットの結果を生成するたびに、その結​​果をaddq, adcq,adcq命令で 64 ビット レジスタの累積トリプルに加算します (ここで、最後のadcq命令はキャリー フラグが伝搬されることを保証するためにゼロを追加するだけです)。

テストとして小さな負の数を小さな正の数で乗算すると、結果は負になりますが、128 ビットの結果の上位 64 ビット値の下部にゼロ以外のビットが 1 つまたは 2 つありますs0128。これは、多倍精度の符号付き乗算の伝播で何かが正しくないことを意味します。

s0256もちろん、カスケードは、s0512、の場合はかなり広範囲ですs1024

私は何が欠けていますか?両方のオペランドを符号なしに変換し、符号なし乗算を実行してから、オペランドの一方 (両方ではない) が負の場合、結果を否定する必要がありますか? imulまたは、符号付き乗算命令を使用して多倍精度の結果を計算できますか?

4

1 に答える 1

4

小さい乗算から拡張精度の符号付き乗算を作成すると、符号付き演算と符号なし演算が混在することになります。

特に、符号付きの値を半分に分割すると、上半分を符号付きとして扱い、下半分を符号なしとして扱います。実際、同じことが拡張精度の加算にも当てはまります。

この任意の例を考えてみましょう。ここで、AHALは の上半分と下半分を表し、 と は の上半分と下半分ABH表しBLます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 * BHとのAH * 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 つ以上の部分に分割した場合でも拡張されます。つまり、符号付きの数値の最も重要な部分だけが符号付きとして扱われます。他の作品はすべて無署名です。各被乗数を 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 に答える