web-dev-qa-db-ja.com

コンパイラが128ビット整数をサポートしていない場合、CまたはC ++で128ビット整数を加算および減算するにはどうすればよいですか?

私は128ビット数の長いストリーム用のコンプレッサーを書いています。数値を差として保存したいのですが、数値自体ではなく、数値間の差のみを格納します。これは、数値が小さいため、より少ないバイト数で差分をパックできるためです。

ただし、圧縮の場合はこれらの128ビット値を減算する必要があり、解凍の場合はこれらの値を加算する必要があります。私のコンパイラの最大整数サイズは64ビット幅です。

これを効率的に行うためのアイデアはありますか?

24
Billy ONeal

必要なのが加算と減算だけで、128ビット値がすでにバイナリ形式である場合、ライブラリは便利かもしれませんが、厳密には必要ありません。この数学は自分で行うのは簡単です。

コンパイラが64ビット型に何を使用するかわからないため、符号付きおよび符号なしの64ビット整数量にはINT64とUINT64を使用します。

class Int128
{
public:
    ...
    Int128 operator+(const Int128 & rhs)
    {
        Int128 sum;
        sum.high = high + rhs.high;
        sum.low = low + rhs.low;
        // check for overflow of low 64 bits, add carry to high
        if (sum.low < low)
            ++sum.high;
        return sum;
    }
    Int128 operator-(const Int128 & rhs)
    {
        Int128 difference;
        difference.high = high - rhs.high;
        difference.low = low - rhs.low;
        // check for underflow of low 64 bits, subtract carry to high
        if (difference.low > low)
            --difference.high;
        return difference;
    }

private:
    INT64  high;
    UINT64 low;
};
38
Mark Ransom

[〜#〜] gmp [〜#〜] を見てください。

#include <stdio.h>
#include <gmp.h>

int main (int argc, char** argv) {
    mpz_t x, y, z;
    char *xs, *ys, *zs;
    int i;
    int base[4] = {2, 8, 10, 16};

    /* setting the value of x in base 10 */
    mpz_init_set_str(x, "100000000000000000000000000000000", 10);

    /* setting the value of y in base 16 */
    mpz_init_set_str(y, "FF", 16);

    /* just initalizing the result variable */
    mpz_init(z);

    mpz_sub(z, x, y);

    for (i = 0; i < 4; i++) {
        xs = mpz_get_str(NULL, base[i], x);
        ys = mpz_get_str(NULL, base[i], y);
        zs = mpz_get_str(NULL, base[i], z);

        /* print all three in base 10 */
        printf("x = %s\ny = %s\nz = %s\n\n", xs, ys, zs);

        free(xs);
        free(ys);
        free(zs);
    }

    return 0;
}

出力は

x = 10011101110001011010110110101000001010110111000010110101100111011111000000100000000000000000000000000000000
y = 11111111
z = 10011101110001011010110110101000001010110111000010110101100111011111000000011111111111111111111111100000001

x = 235613266501267026547370040000000000
y = 377
z = 235613266501267026547370037777777401

x = 100000000000000000000000000000000
y = 255
z = 99999999999999999999999999999745

x = 4ee2d6d415b85acef8100000000
y = ff
z = 4ee2d6d415b85acef80ffffff01
16
Chas. Owens

この比較的古い投稿に偶然出くわしたので、経験の浅い読者のために、Volteの以前の推測を詳しく説明するのが適切だと思いました。

まず、128ビット数の符号付き範囲は-2です。127 2に127-2ではなく-1127 2に127 当初の規定どおり。

第二に、有限算術の周期的な性質により、2つの128ビット数の間に必要な最大の差は-2です。127 2に127-1、これは129ではなく128ビットのストレージ前提条件を持っています。ただし(2127-1)-(-2127)= 2128-1これは明らかに最大値2よりも大きい127-1の正の整数、算術オーバーフローは常に任意の2つの間の最も近い距離を保証します n-ビット数は常に0から2の範囲内にありますn-1、したがって暗黙的に-2n-1 2にn-1-1。

明確にするために、最初に、架空の3ビットプロセッサが2進加算を実装する方法を調べてみましょう。例として、3ビット整数の絶対符号なし範囲を示す次の表について考えてみます。

0 = 000b
1 = 001b
2 = 010b
3 = 011b
4 = 100b
5 = 101b
6 = 110b
7 = 111b ---> [オーバーフロー時に000bに戻ります]

上記の表から、次のことがすぐにわかります。

001b(1)+ 010b(2)= 011b(3)

これらの数値のいずれかを数値の補数で加算すると、常に2になることも明らかです。n-1:

010b(2)+ 101b([2の補数] = 5)= 111b(7)=(23-1)

2つの加算時に発生する周期的なオーバーフローのため n-ビット数は(n+1)ビットの結果、したがって、これらの数値のいずれかをその数値補数+ 1で加算すると、常に0が生成されます。

010b(2)+ 110b([2の補数] + 1)= 000b(0)

したがって、次のように言うことができます。 n] + 1 =-n、 そのため n + [の補数 n] + 1 = n +(-n)= 0。さらに、それがわかっている場合 n + [の補数 n] + 1 = 0、次に n + [の補数 nバツ] +1マスト= n -(n-バツ)= バツ

これを元の3ビットテーブルに適用すると、次のようになります。

0 = 000b = [0の補数] + 1 = 0
1 = 001b = [7の補数] + 1 = -7
2 = 010b = [6の補数] + 1 = -6
3 = 011b = [5の補数] + 1 = -5
4 = 100b = [4の補数] + 1 = -4
5 = 101b = [3の補数] + 1 = -3
6 = 110b = [2の補数] + 1 = -2
7 = 111b = [1の補数] + 1 = -1 ---> [オーバーフロー時に000bに戻ります]

表現的抽象化が正、負、または符号付き2の補数演算で暗示される両方の組み合わせであるかどうかにかかわらず、2が得られます。nn-正の0から2の両方にシームレスに対応できるビットパターンn-1および負の0から-(2n)-1は、必要に応じて範囲を指定します。実際、最近のすべてのプロセッサは、加算演算と減算演算の両方に共通のALU回路を実装するために、まさにそのようなシステムを採用しています。 CPUがi1 - i2減算命令を検出すると、内部でi2に対して[complement + 1]演算を実行し、その後、i1 + [complement of i2] + 1.追加のキャリー/サインXORゲートオーバーフローフラグを除いて、符号付きと符号なしの両方の加算、および暗黙の減算により、それぞれ暗黙的です。

上記の表を入力シーケンスに適用すると[-2n-1、2n-1-1、-2n-1] Volteの元の回答に示されているように、次のnビット差分を計算できるようになりました。

差分#1:
(2n-1-1)-(-2n-1)=
3-(-4)= 3 + 4 =
(-1)= 7 = 111b

差分#2:
(-2n-1)-(2n-1-1)=
(-4)-3 =(-4)+(5)=
(-7)= 1 = 001b

シード-2から始めますn-1、上記の各差分を順番に適用することで、元の入力シーケンスを再現できるようになりました。

(-2n-1)+(差分#1)=
(-4)+ 7 = 3 =
2n-1-1

(2n-1-1)+(diff#2)=
3 +(-7)=(-4)=
-2n-1

もちろん、この問題に対してより哲学的なアプローチを採用し、その理由について推測することもできます2n 周期的に連続する数には2つ以上が必要ですn 周期的にシーケンシャルな差分?

タリアドン。

8
Taliadon

Boost 1.53には、多倍長が含まれるようになりました。

#include <boost/multiprecision/cpp_int.hpp>
#include <iostream>

// Requires Boost 1.53 or higher
// build: g++ text.cpp

int main()
{
    namespace mp = boost::multiprecision;

    mp::uint128_t a = 4294967296;
    mp::uint256_t b(0);
    mp::uint512_t c(0);

    b = a * a;
    c = b * b;

    std::cout << "c: " << c << "\n";
    return 0;
}

出力:

./a.out
c: 340282366920938463463374607431768211456
8
user1356386

大整数の数学に関する文献はたくさんあります。無料で入手できるライブラリの1つを使用することも(リンクを参照)、独自のライブラリを作成することもできます。警告する必要がありますが、加算と減算(およびシフト)よりも複雑な場合は、重要なアルゴリズムを使用する必要があります。

加算および減算するには、2つの64ビット整数を保持するクラス/構造を作成できます。簡単な学校の数学を使用して、足し算と引き算を行うことができます。基本的には、持ち運びや借り入れを慎重に考慮して、鉛筆と紙を使って足し算や引き算をします。

大きな整数を検索します。ところで、VC++、IntelC++、およびGCCコンパイラの最近のバージョンには128ビット整数型がありますが、必要なだけ簡単にアクセスできるかどうかはわかりません(sse2/xmmsレジスタで使用することを目的としています)。

3
Ash

TomsFastMath はGMP(上記)に少し似ていますが、パブリックドメインであり、非常に高速になるようにゼロから設計されています(x86、x86-64、ARMのアセンブリコードの最適化も含まれています。 SSE2、PPC32、およびAVR32)。

2
Sophie Alpert

また、注目に値するのは、数値のストリームを前処理して圧縮を改善することだけが目的の場合、前処理されたストリームを正確に算術的な違いで作成する必要はないということです。 XOR(^) の代わりに +および-。利点は、128ビットXORが64ビット部分の2つの独立したXORとまったく同じであるため、単純で効率的であるということです。

0
Armin Rigo