私はCを学ぼうとしていますが、本当に大きな数字(100桁、1000桁など)を扱うことができないことに遭遇しました。これを行うライブラリが存在することは承知していますが、自分で実装を試みたいと思います。
誰かが任意精度の算術演算について非常に詳細で馬鹿げた説明を持っているか、提供できるかどうかを知りたいだけです。
数値をより小さな部分として扱うには、適切なストレージとアルゴリズムの問題です。 int
が0から99までしか使用できないコンパイラーがあり、最大999999までの数値を処理すると仮定します(ここでは、単純にするために正の数値のみを心配します)。
それには、各番号に3つのint
sを与え、加算、減算、およびその他の基本操作のために小学校で学習したはずの同じルールを使用します。
任意精度のライブラリでは、メモリを保持できるものであれば何でも、数値を表すために使用される基本型の数に固定の制限はありません。
例:123456 + 78
:
12 34 56
78
-- -- --
12 35 34
最下位から作業する:
これは実際、CPU内のビットレベルで加算が一般にどのように機能するかです。
減算は似ています(基本型の減算とキャリーの代わりにボローを使用)、乗算は繰り返し加算(非常に遅い)またはクロス積(高速)で実行でき、除算はより複雑ですが、数値のシフトと減算で実行できます関与(子供の頃に学んだであろう長い区分)。
実際には、2乗時に整数に収まる最大10の累乗を使用してこの種の処理を行うライブラリを作成しました(2つのint
sを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
は負の数を表し、a
とb
はゼロまたは正の数であると仮定します。
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 / 27
は457
で、残りは6
です。確認:
457 x 27 + 6
= 12339 + 6
= 12345
これは、ドローダウン変数(最初はゼロ)を使用して、12345のセグメントを27以上になるまで一度に1つずつダウンさせることで実装されます。
次に、27を下回るまで単純に27を減算します。減算の数は、一番上の行に追加されたセグメントです。
停止するセグメントがなくなると、結果が得られます。
これらは非常に基本的なアルゴリズムであることに注意してください。数値が特に大きくなる場合は、複雑な算術を行うより良い方法があります。 GNU Multiple Precision Arithmetic Library のようなものを見ることができます-それは私自身のライブラリよりもかなり良くて速いです。
メモリが足りなくなると単純に終了するという不運な機能があります(私の意見では、汎用ライブラリにとっては致命的な欠陥です)。
ライセンス上の理由で使用できない場合(または明白な理由もなくアプリケーションを終了させたくないため)、少なくともそこからアルゴリズムを取得して、独自のコードに統合することができます。
[〜#〜] mpir [〜#〜] (GMPの分岐点)の境界は、潜在的な変更についての議論を受け入れやすいこともわかりました。束。
車輪を再発明することはあなたの個人的な啓発と学習にとって非常に良いことですが、それは非常に大きな仕事でもあります。その重要な演習であり、自分でやったことだと思わせたくはありませんが、大規模なパッケージで対処している作業には微妙で複雑な問題があることに注意してください。
たとえば、乗算。単純に、「男子生徒」メソッドを考えてみてください。つまり、ある数字を別の数字の上に書いてから、学校で学んだように長い掛け算をします。例:
123
x 34
-----
492
+ 3690
---------
4182
ただし、この方法は非常に低速です(O(n ^ 2)、nは桁数です)。代わりに、最新のbignumパッケージは、離散フーリエ変換または数値変換のいずれかを使用して、これを本質的にO(n ln(n))操作に変換します。
これは整数用です。数値の実数表現(log、sqrt、expなど)でより複雑な関数に入ると、事態はさらに複雑になります。
理論的な背景が必要な場合は、Yapの本の最初の章 "アルゴリズム代数の基本問題" を読むことを強くお勧めします。すでに述べたように、gmp bignumライブラリは優れたライブラリです。実数については、私はmpfrを使用し、気に入っています。
車輪を再発明しないでください:正方形になるかもしれません!
GNU MP などの試行およびテスト済みのサードパーティライブラリを使用します。
基本的には鉛筆と紙で行うのと同じ方法で行います...
malloc
とrealloc
を使用することを意味する)をとることができるバッファー(配列)で表されます。通常、計算の基本単位として使用します
アーキテクチャによって決まります。
2進法または10進法の選択は、スペース効率の最大化、人間の可読性、およびチップでのBinary Coded Decimal(BCD)数学サポートの不在の有無によって異なります。
あなたは数学の高校レベルでそれを行うことができます。実際には、より高度なアルゴリズムが使用されています。したがって、たとえば、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をご覧になることをお勧めします。
究極のリファレンス(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},
}
自分で大きな整数コードを書きたいと仮定すると、これは驚くほど簡単で、最近やった人として話されます(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に減らしてみてください。
以下は、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);
}