大きな整数の計算

配列を利用して大きな整数の計算を可能にしてみよう。

整数型変数の限界

普段使用している int 型は、実質long 型 つまり32ビットで表される範囲の整数である。この上限は(符号つきであれば) 2147483647 (約21億; 10桁)である。long long にしたとしても20桁 の数が上限である。

ではここで問題。111111111111111111111111111111 (30個の1)と 222222222222222222222222222222 (30個の2)を足すといくつだろう? 答えは簡単、333333333333333333333333333333 (30個の3)である。 算数を習っていれば分かる計算である。しかし、C言語でint型変数を使っても 30桁になる整数を扱うことができない。

では、完全に不可能かというとそうではない。人間が筆算をするときのよう に計算させると何桁でも足し算できる。たとえば、25桁と25桁の二つの数値
3634654235432438943534512 と 2345132546578263422154652 を足す場合を考えよう。人間が足し算する場合、 1の位から1桁ずつ見ながら足し算して、足した結果が10以上になったら 10の位の数値を左隣の桁に繰り上げて行く。

           111  111     1     (くり上がり)
   3634654235432438943534512
 +)2345132546578263422154652
------------------------------
   5979786782012702365689164

この「筆算」のやり方は桁数が100桁になっても200桁になっても通用する。

多倍長演算

数を表すのに、変数一つだけでなく、複数の変数を組み合わせて非常に大き な桁の数を扱えるようにして計算することを多倍長演算という。

たとえば、一つの変数が表す10進数を0〜9の一桁だけと決めておいて、 配列を使って何桁も表す。

char x[10];            /* 10個の char (8ビット) 変数 */
x[09] x[08] x[07] x[06] x[05] x[04] x[03] x[02] x[01] x[00]
2 0 4 4 6 2 8 3 5 1

これを10進数 2044628351 だと思うことにする。各配列の要素は 全て char 型変数なので本来は -128 〜 -127 までの整数を表現できるが、 「全て10進数の1桁を表すのだ」、というルールの元に、0〜9の値以外は 代入しないようにする。

さて、2044628351 という数に、同じ10桁の数を足してみよう。 3173648074 を表す配列 y[] を用意して、x[] の表す数と足してみよう。

「1の位から計算します!」

配列 x[ ] 2 0 4 4 6 2 8 3 5 1
配列 y[ ] 3 1 7 3 6 4 8 0 7 4
こたえ








5

「1たす4は、5」

1の位が終わったら、10の位。

「1の位から計算します!」

配列 x[ ] 2 0 4 4 6 2 8 3 5 1
配列 y[ ] 3 1 7 3 6 4 8 0 7 4
こたえ







12 5

「5たす7は、12」

となるところだが、筆算では足した結果が10以上になったら、 10の位を繰り上がりとして上に書いておく。多倍長演算でも、 全ての配列要素は「0〜9だけ!」というルールであることは変わらないので、 結果が10以上になったら1の位だけ配列要素に代入し、10の位を次の位に繰り越 す。「1の位」、「10の位」は、C言語のintの計算でいえばそれぞれ、 「10で割った余り」、「10で割った商」に相当する。

これを考慮して、「5たす7は、12」を繰り上げにしよう。

繰り上がり






1

配列 x[ ] 2 0 4 4 6 2 8 3 5 1
配列 y[ ] 3 1 7 3 6 4 8 0 7 4
こたえ







2 5
12 / 10 = 1	(intは切り捨て)
12 % 10 = 2	(剰余演算)

次の桁は、繰り上がりと x[], y[] を足す。

「100の位を計算します」

繰り上がり






1

配列 x[ ] 2 0 4 4 6 2 8 3 5 1
配列 y[ ] 3 1 7 3 6 4 8 0 7 4
こたえ






4
2 5

1たす3たす0は、4」

後は説明しなくても分かるだろう。1000の位の「8足す8」では、 16ではなく、10で割った余りの6を「こたえ」に、10で割って切り捨てた1を 「繰り上がり」にいれる。これをくり返すと計算結果は以下のようになる。

配列の添字 9 8 7 6 5 4 3 2
1 0
繰り上がり
1
1
1
1

配列 x[ ] 2 0 4 4 6 2 8 3 5 1
配列 y[ ] 3 1 7 3 6 4 8 0 7 4
こたえ 5 2 1 8 2 7 6 4
2 5

配列を用いた多倍長演算

上記の「筆算」の手順を、C言語プログラムにするには、以下のような流れで とらえる。

  1. 足したい数2つを多倍長として配列にいれる
  2. 配列の添字0を1の位と考える
  3. 添字が上がるにつれて、10の位、100の位、……と上がって行くものとする
  4. 同じ添字の数を足し合わせる
  5. 足した数の
  6. 添字を1ずつ増やして上記の計算をくり返す。
  7. 最後の添字まで計算したら、答の配列を大きな添字から読むとそれが答

まずは簡単のため、x と y の配列はあらかじめ数値をいれておく。

/* 2044628351 を要素10個の配列 x に。添字0から1の位 */
char x[] = {1, 5, 3, 8, 2, 6, 4, 4, 0, 2};
/* 3173648074 を要素10個の配列 y に。添字0から1の位 */
char y[] = {4, 7, 0, 8, 4, 6, 3, 7, 1, 3};
/* 答えを入れる配列 z[]; */
char z[10];

1の位、つまり添字0から足し算して行く。添字の繰り返しは0から9。

int i, sum;		/* sum は各桁を足した結果一時保存 */
int kuriage=0;		/* 繰り上げを保存 */
for (i=0; i<10; i++) {
  sum = x[i]+y[i]+kuriage;
  z[i] = sum%10;	/* 10で割った余り == その桁の答 */
  kuriage = sum/10;	/* 10で割った商 == 繰り上げ */
}

上記のループで1の位から109の位まで筆算が終わる。

最後に、答となっている配列 z を添字の大きい方から 出力する。

for (i=9; i>=0; i--) {
  printf("%d", z[i]);
}
puts("");		/* 改行のみ出力 */

以上のプログラムをまとめると以下のようになる。

10keta.c

#include <stdio.h>

int main()
{
  /* 2044628351 を要素10個の配列 x に。添字0から1の位 */
  char x[] = {1, 5, 3, 8, 2, 6, 4, 4, 0, 2};
  /* 3173648074 を要素10個の配列 y に。添字0から1の位 */
  char y[] = {4, 7, 0, 8, 4, 6, 3, 7, 1, 3};
  /* 答えを入れる配列 z[]; */
  char z[10];
  
  int i, sum;		/* sum は各桁を足した結果一時保存 */
  int kuriage=0;		/* 繰り上げを保存 */
  for (i=0; i<10; i++) {
    sum = x[i]+y[i]+kuriage;
    z[i] = sum%10;	/* 10で割った余り == その桁の答 */
    kuriage = sum/10;	/* 10で割った商 == 繰り上げ */
  }

  for (i=9; i>=0; i--) {
    printf("%d", z[i]);
  }
  puts("");		/* 改行のみ出力 */

}

上記のプログラムでは、二つの大きな数値を書くのにプログラム中に 埋め込まなければならない。違う数値の足し算をしたかったら、 プログラムを書き換えてまたコンパイルし直さなければならず、 全く実用的でない。ユーザが入力した(巨大な)数値を多倍長配列 に読み取れるようにするにはどうしたらいいだろうか。


本日の目次