web-dev-qa-db-ja.com

任意精度の演算説明

私はCを学ぼうとしていますが、本当に大きな数字(100桁、1000桁など)を扱うことができないことに遭遇しました。これを行うライブラリが存在することは承知していますが、自分で実装を試みたいと思います。

誰かが任意精度の算術演算について非常に詳細で馬鹿げた説明を持っているか、提供できるかどうかを知りたいだけです。

88
TT.

数値をより小さな部分として扱うには、適切なストレージとアルゴリズムの問​​題です。 intが0から99までしか使用できないコンパイラーがあり、最大999999までの数値を処理すると仮定します(ここでは、単純にするために正の数値のみを心配します)。

それには、各番号に3つのintsを与え、加算、減算、およびその他の基本操作のために小学校で学習したはずの同じルールを使用します。

任意精度のライブラリでは、メモリを保持できるものであれば何でも、数値を表すために使用される基本型の数に固定の制限はありません。

例:123456 + 78

12 34 56
      78
-- -- --
12 35 34

最下位から作業する:

  • 初期キャリー= 0。
  • 56 + 78 + 0キャリー= 134 = 34、1キャリー
  • 34 + 00 + 1キャリー= 35 = 35、キャリー0
  • 12 + 00 + 0キャリー= 12 = 12キャリー0で

これは実際、CPU内のビットレベルで加算が一般にどのように機能するかです。

減算は似ています(基本型の減算とキャリーの代わりにボローを使用)、乗算は繰り返し加算(非常に遅い)またはクロス積(高速)で実行でき、除算はより複雑ですが、数値のシフトと減算で実行できます関与(子供の頃に学んだであろう長い区分)。

実際には、2乗時に整数に収まる最大10の累乗を使用してこの種の処理を行うライブラリを作成しました(2つのintsを16ビットint 0から99に制限され、2乗時に9,801(<32,768)を生成するか、32ビットintを使用して0から9,999を使用して99,980,001(<2,147,483,648)を生成し、アルゴリズムを大幅に緩和しました。

注意すべきいくつかのトリック。

1 /数値を追加または乗算する際に、必要な最大スペースを事前に割り当てて、多すぎる場合は後で削減します。たとえば、2つの100桁の数字(数字はint)を追加しても、101桁を超えることはありません。 12桁の数字と3桁の数字を乗算しても、15桁を超えることはありません(桁数を加算します)。

2 /速度を上げるために、絶対に必要な場合にのみ数値を正規化(必要なストレージを削減)します。ユーザーの速度とストレージの懸念をユーザーが判断できるように、ライブラリに個別の呼び出しがあります。

3 /正数と負数の加算は減算であり、負数の減算は同等の正数の加算と同じです。符号を調整した後、addメソッドと減算メソッドを相互に呼び出すことにより、かなりのコードを節約できます。

4 /常に次のような数字になるため、小さな数字から大きな数字を引くことは避けてください。

         10
         11-
-- -- -- --
99 99 99 99 (and you still have a borrow).

代わりに、11から10を減算し、それを否定します。

11
10-
--
 1 (then negate to get -1).

これは、私がこれをしなければならなかったライブラリの1つからのコメント(テキストに変換)です。コード自体は、残念ながら著作権で保護されていますが、4つの基本操作を処理するのに十分な情報を選択できる場合があります。以下では、-a-bは負の数を表し、abはゼロまたは正の数であると仮定します。

additionの場合、符号が異なる場合は、否定の減算を使用します。

-a +  b becomes b - a
 a + -b becomes a - b

subtractionの場合、符号が異なる場合は、否定の追加を使用します。

 a - -b becomes   a + b
-a -  b becomes -(a + b)

また、大きな数値から小さな数値を減算するための特別な処理:

small - big becomes -(big - small)

Multiplicationは、エントリレベルの数学を次のように使用します。

475(a) x 32(b) = 475 x (30 + 2)
               = 475 x 30 + 475 x 2
               = 4750 x 3 + 475 x 2
               = 4750 + 4750 + 4750 + 475 + 475

これを実現する方法には、32の各桁を一度に1つずつ抽出し(逆方向)、addを使用して結果に追加する値(最初はゼロ)を計算することが含まれます。

ShiftLeftおよびShiftRight操作は、LongIntをラップ値(「実際の」数学の場合は10)ですばやく乗算または除算するために使用されます。上の例では、ゼロに475を2回(最後の32桁)追加して950を取得します(結果= 0 + 950 = 950)。

次に、シフト475を出て4750を取得し、右シフト32を取得して3を取得します。4750をゼロに3回追加して14250を取得し、950の結果に追加して15200を取得します。

左シフト4750で47500、右シフト3で0になります。右シフト32がゼロになったので、終了し、実際には475 x 32は15200になります。

Divisionも扱いにくいですが、初期の算術に基づいています( "goes into"の "gazinta"メソッド)。 12345 / 27の次の長い区分を考慮してください。

       457
   +-------
27 | 12345    27 is larger than 1 or 12 so we first use 123.
     108      27 goes into 123 4 times, 4 x 27 = 108, 123 - 108 = 15.
     ---
      154     Bring down 4.
      135     27 goes into 154 5 times, 5 x 27 = 135, 154 - 135 = 19.
      ---
       195    Bring down 5.
       189    27 goes into 195 7 times, 7 x 27 = 189, 195 - 189 = 6.
       ---
         6    Nothing more to bring down, so stop.

したがって、12345 / 27457で、残りは6です。確認:

  457 x 27 + 6
= 12339    + 6
= 12345

これは、ドローダウン変数(最初はゼロ)を使用して、12345のセグメントを27以上になるまで一度に1つずつダウンさせることで実装されます。

次に、27を下回るまで単純に27を減算します。減算の数は、一番上の行に追加されたセグメントです。

停止するセグメントがなくなると、結果が得られます。


これらは非常に基本的なアルゴリズムであることに注意してください。数値が特に大きくなる場合は、複雑な算術を行うより良い方法があります。 GNU Multiple Precision Arithmetic Library のようなものを見ることができます-それは私自身のライブラリよりもかなり良くて速いです。

メモリが足りなくなると単純に終了するという不運な機能があります(私の意見では、汎用ライブラリにとっては致命的な欠陥です)。

ライセンス上の理由で使用できない場合(または明白な理由もなくアプリケーションを終了させたくないため)、少なくともそこからアルゴリズムを取得して、独自のコードに統合することができます。

[〜#〜] mpir [〜#〜] (GMPの分岐点)の境界は、潜在的な変更についての議論を受け入れやすいこともわかりました。束。

156
paxdiablo

車輪を再発明することはあなたの個人的な啓発と学習にとって非常に良いことですが、それは非常に大きな仕事でもあります。その重要な演習であり、自分でやったことだと思わせたくはありませんが、大規模なパッケージで対処している作業には微妙で複雑な問題があることに注意してください。

たとえば、乗算。単純に、「男子生徒」メソッドを考えてみてください。つまり、ある数字を別の数字の上に書いてから、学校で学んだように長い掛け算をします。例:

      123
    x  34
    -----
      492
+    3690
---------
     4182

ただし、この方法は非常に低速です(O(n ^ 2)、nは桁数です)。代わりに、最新のbignumパッケージは、離散フーリエ変換または数値変換のいずれかを使用して、これを本質的にO(n ln(n))操作に変換します。

これは整数用です。数値の実数表現(log、sqrt、expなど)でより複雑な関数に入ると、事態はさらに複雑になります。

理論的な背景が必要な場合は、Yapの本の最初の章 "アルゴリズム代数の基本問題" を読むことを強くお勧めします。すでに述べたように、gmp bignumライブラリは優れたライブラリです。実数については、私はmpfrを使用し、気に入っています。

8
froofroo

車輪を再発明しないでください:正方形になるかもしれません!

GNU MP などの試行およびテスト済みのサードパーティライブラリを使用します。

6
Mitch Wheat

基本的には鉛筆と紙で行うのと同じ方法で行います...

  • 番号は、必要に応じて任意のサイズ(mallocreallocを使用することを意味する)をとることができるバッファー(配列)で表されます。
  • 言語でサポートされている構造を使用して、可能な限り基本的な算術演算を実装し、基数ポイントのキャリーと移動を手動で処理する
  • 数値解析テキストを精査して、より複雑な機能で処理するための効率的な引数を見つけます。
  • 必要なだけ実装します。

通常、計算の基本単位として使用します

  • 0-99または0-255を含むバイト
  • 0-9999または0--65536を含む16ビットワード
  • 次を含む32ビットワード...
  • ...

アーキテクチャによって決まります。

2進法または10進法の選択は、スペース効率の最大化、人間の可読性、およびチップでのBinary Coded Decimal(BCD)数学サポートの不在の有無によって異なります。

4
dmckee

あなたは数学の高校レベルでそれを行うことができます。実際には、より高度なアルゴリズムが使用されています。したがって、たとえば、2つの1024バイトの数値を追加するには:

unsigned char first[1024], second[1024], result[1025];
unsigned char carry = 0;
unsigned int  sum   = 0;

for(size_t i = 0; i < 1024; i++)
{
    sum = first[i] + second[i] + carry;
    carry = sum - 255;
}

最大値を処理するために追加する場合、結果はone placeだけ大きくする必要があります。これを見てください:

9
   +
9
----
18

TTMath は、学びたいなら素晴らしいライブラリです。 C++を使用して構築されています。上記の例は愚かなものでしたが、これが加算と減算の一般的な方法です!

この主題に関する適切なリファレンスは、 数学演算の計算の複雑さ です。実装する各操作に必要なスペースがわかります。たとえば、2つのN-digit番号がある場合、乗算の結果を保存するには2N digitsが必要です。

Mitchが言ったように、実装するのは決して簡単な作業ではありません! C++を知っているなら、TMTMathをご覧になることをお勧めします。

3
AraK

究極のリファレンス(IMHO)の1つは、KnuthのTAOCP Volume IIです。これらの表現で数値と算術演算を表現するための多くのアルゴリズムについて説明します。

@Book{Knuth:taocp:2,
   author    = {Knuth, Donald E.},
   title     = {The Art of Computer Programming},
   volume    = {2: Seminumerical Algorithms, second edition},
   year      = {1981},
   publisher = {\Range{Addison}{Wesley}},
   isbn      = {0-201-03822-6},
}
3
Marc van Dongen

自分で大きな整数コードを書きたいと仮定すると、これは驚くほど簡単で、最近やった人として話されます(MATLABで)。

  • 個々の10進数を2進数として保存しました。これにより、多くの操作、特に出力が簡単になります。必要以上に多くのストレージを使用しますが、ここではメモリが安価であり、1組のベクトルを効率的に畳み込むことができれば乗算が非常に効率的になります。または、複数の10進数をdoubleに格納できますが、乗算を実行するための畳み込みが非常に大きな数値で数値の問題を引き起こす可能性があることに注意してください。

  • 符号ビットを個別に保存します。

  • 2つの数値の加算は、主に数字を加算してから、各ステップで桁上げを確認することです。

  • 少なくともタップで高速の畳み込みコードを使用している場合、数値のペアの乗算は、畳み込みの後にキャリーステップが続くようにするのが最適です。

  • 数値を個々の10進数の文字列として格納する場合でも、除算(mod/rem ops)を実行して、結果で一度に約13個の10進数を取得できます。これは、一度に1桁の1桁のみで機能する除算よりもはるかに効率的です。

  • 整数の整数のべき乗を計算するには、指数のバイナリ表現を計算します。次に、必要に応じて、繰り返し二乗演算を使用してべき乗を計算します。

  • 多くの操作(ファクタリング、素数性テストなど)は、powermod操作の恩恵を受けます。つまり、mod(a ^ p、N)を計算するとき、pがバイナリ形式で表されているべき乗の各ステップで結果mod Nを減らします。最初にa ^ pを計算してから、mod Nに減らしてみてください。

1
user85109

以下は、PHPで行った単純な(単純な)例です。

「Add」と「Multiply」を実装し、指数の例に使用しました。

http://adevsoft.com/simple-php-arbitrary-precision-integer-big-num-example/

コードスニップ

// Add two big integers
function ba($a, $b)
{
    if( $a === "0" ) return $b;
    else if( $b === "0") return $a;

    $aa = str_split(strrev(strlen($a)>1?ltrim($a,"0"):$a), 9);
    $bb = str_split(strrev(strlen($b)>1?ltrim($b,"0"):$b), 9);
    $rr = Array();

    $maxC = max(Array(count($aa), count($bb)));
    $aa = array_pad(array_map("strrev", $aa),$maxC+1,"0");
    $bb = array_pad(array_map("strrev", $bb),$maxC+1,"0");

    for( $i=0; $i<=$maxC; $i++ )
    {
        $t = str_pad((string) ($aa[$i] + $bb[$i]), 9, "0", STR_PAD_LEFT);

        if( strlen($t) > 9 )
        {
            $aa[$i+1] = ba($aa[$i+1], substr($t,0,1));
            $t = substr($t, 1);
        }

        array_unshift($rr, $t);
     }

     return implode($rr);
}
0
kervin