6

最初のちょっとした経歴:
-私は初めてのポスターで、大学の学生です(プログラミングではありません)。
-これは宿題の質問ではありません。私は楽しみのためにこれをしているだけです。
-私のプログラミング経験は、C ++の1学期(3か月)と高校でのQBasicで構成されています。
-はい、GMPおよびBignumライブラリを確認しました。特にプログラマーの意図を理解せずに、生のコードから何かを学ぶことは非常に困難です。その上、私は自分でそれを行う方法を学びたいです。

任意の大きさの整数の乗算関数をコーディングしています。これらの数字を表すために文字配列を使用しており、末尾に+または-を付けて番兵として機能させています(例:「12345 +」、「31415-」)。

現在、カラツバアルゴリズムを実装しています。問題は、再帰と動的メモリ割り当てを使用すると、関数が単純な方法よりも5倍遅くなることです。
実行時間を短縮する方法について、いくつかのヒントを使用できます。

char* dam(char* one, char* two){            // Karatsuba method

    char* zero = intochar(0, 0);
    int size_a = char_size(one) - 1;
    int size_b = char_size(two) - 1;

    if(compare(one, zero) == 0 || compare(two, zero) == 0)
        return zero;                        // if either array is zero, product is zero
    delete[] zero;

    if(size_a < 4 && size_b < 4)            // if both numbers are 3 digits or less, just return their product
        return multiplication(one, two);
                                            // is the product negative?
    bool negative = one[size_a] == two[size_b]? false : true;
    int digits = size_a > size_b ? size_a : size_b;
    digits += digits & 1;                   // add one if digits is odd
    int size = digits / 2 + 1;              // half the digits plus sentinel

    char* a, *b;                            // a and b represent one and two but with even digits
    if(size_a != digits)
        a = pad_char(one, digits - size_a); // pad the numbers with leading zeros so they have even digits
    else
        a = copy_char(one);
    if(size_b != digits)
        b = pad_char(two, digits - size_b);
    else
        b = copy_char(two);

    char* a_left = new char[size];          // left half of number a
    char* a_rite = new char[size];          // right half of number a
    char* b_left = new char[size];
    char* b_rite = new char[size];
    memcpy(a_left, a, size - 1);
    a_left[size - 1] = a[digits];
    memcpy(a_rite, a + size - 1, size);
    memcpy(b_left, b, size - 1);
    b_left[size - 1] = b[digits];
    memcpy(b_rite, b + size - 1, size);
    delete[] a;
    delete[] b;

    char* p0 = dam(a_left, b_left);         // Karatsuba product = p1*10^n + (p0+p2-p1)*10^(n/2) + p2
    char* p2 = dam(a_rite, b_rite);
    deduct(a_left, a_rite);
    deduct(b_left, b_rite);
    char* p1 = dam(a_left, b_left);
    char* p3 = intochar(0, digits - 1);     // p3 = p0 + p2 - p1
    append(p3, p0);                         // append does naive addition
    append(p3, p2);
    deduct(p3, p1);
    delete[] a_left;
    delete[] a_rite;
    delete[] b_left;
    delete[] b_rite;

    int sum_size = 2 * digits;              // product of two numbers can have a maximum of n1 + n2 digits
    char* sum = new char[sum_size + 1];
    memset(sum, 0, sum_size);
    if(negative)
        sum[sum_size] = '-';
    else
        sum[sum_size] = '+';

    char* left = extend_char(p0, digits, false);        // extend returns a new array with trailing zeros
    char* mid = extend_char(p3, size - 1, false);
    append(sum, left);
    append(sum, mid);
    append(sum, p2);
    delete[] p0;
    delete[] p1;
    delete[] p2;
    delete[] p3;
    delete[] left;
    delete[] mid;

    return sum;}
4

4 に答える 4

6

Karatsubaは優れたアルゴリズムであり、プログラミングはそれほど難しくありません。楽しみのためだけにやっているのなら、ベース10で作業するのは悪い考えではありません。それはひどく遅くなりますが、素朴な実装も遅くなるので、2つの方法を比較するための基礎があります。

ただし、再帰ツリーのすべてのノードでワークスペースを動的に割り当てて解放するという考えを絶対に放棄する必要があります。あなたはそれを買う余裕がありません。計算の開始時に必要なワークスペースを割り当て、ツリーのすべてのレベルが割り当てなくても必要なワークスペースを取得できるように、ポインターをインテリジェントに処理する必要があります。

また、すべてのレベルでネガティブ製品をテストすることは意味がありません。トップレベルでそれを実行し、計算中は正の数のみを使用して作業します。

それはあなたの質問に関連しているというわけではありませんが、私が次のようなものを見たときはいつでも

bool negative = one[size_a] == two[size_b]? false : true;

私の心は少し縮みます。それらすべての無駄なピクセルについて考えてください!私は敬意を表して提案します:

bool negative = one[size_a] != two[size_b] ;
于 2011-11-02T19:46:16.203 に答える
1

減速は実際には割り当てである可能性があると思います。これらをローカルの固定サイズのバッファに置き換えると、かなりの速度の向上が見られると思います。または、カスタムプールアロケータを使用します。しかし、あなたが巧妙であれば、これの多くはその場で行うことができると思います。

また、文字列の長さを関数に渡すと、毎回長さを見つけるための反復を節約できます。

(また、「正しい」と綴られています)。

于 2011-11-02T19:10:25.950 に答える
1

スペルアウトされた10進値を使用すると、かなりのオーバーヘッドが発生します。カラツバ乗算は、マシンのレジスタサイズに比べて巨大な数値の長い乗算に勝るだけです。実際には、各プリミティブ乗算で可能な限り多くの作業を実行する必要があります。

次のようにデータ構造を再設計することをお勧めします。

if(size_a < 4 && size_b < 4)
    return multiplication(one, two);

このようなものになることができます:

if(size_a == 1 && size_b == 1)
    return box(int64_t(one[0]) * two[0]);

のタイプone[0]int32_t、おそらくです。これは、GMPがそのmp_limb_tアレイで行っていることです。

于 2011-11-02T19:31:46.327 に答える
1

「関数が5倍遅い」とはどういう意味ですか?カラツバは、単に速いだけでなく、漸近的に速くなります。これは、カラツバのおもちゃの実装でさえ、最終的には素朴な乗算よりも速くなることを意味します。10000桁の数字で速度をテストしましたか?

GMPコードは読みにくいことは知っていますが、コードから抽出したこの表を見てください。これは、(異なるCPUに対して)Toom-2,2(カラツバ)のしきい値を示します。簡単に言うと、GMPでのカラツバの実装は、320ビット(10 x 32ビットレジスタ)よりも小さいオペランドの単純な実装よりも高速ではありません。

コードに関するいくつかの質問:

  • 本当にchar*a、* bが必要ですか?計算を開始する前にそれらを削除してください!;-)
  • サインを{a、b} _ {left、rite}にコピーする必要がありますか?結果が負のオペランドで正しいかどうかを確認しましたか?
  • 非常に不均衡なオペランドを検討してください...size_a* 2 <size_b(またはその逆)の場合はどうすればよいですか?

次のステップはToomですよね?

于 2011-11-03T06:56:12.990 に答える