オープンキャンパス模擬講義
「コンピュータの計算は正しくない!」
静岡理工科大学 情報学部
コンピュータシステム学科
幸谷 智紀(こうや とものり)

1.「数」とは?
現在のICT社会で使用される「データ(data)」は例外なく全て「数」を表現したものです。小学校では自然数,分数(有理数),小数を,中学校では負の数と整数を,高校では無理数を習いますが,それらの数を総称して「実数(real number)」と呼びます。実数を幾何学的に表現する時には直線=数直線を使用します。全ての数は,この数直線上のどこかに「点(point)」として存在しています。

自然数,整数,有理数は全て分数として表現できる数です。下記の図に示す通り,分数は無数に存在します。

ちょっと脱線しますが,下記のような問題はご存知でしょうか?

解答については検索して調べて頂ければ何らかの手かがりが得られるでしょうが,実際の分数の計算はこのようにしてはいけませんね。どのように計算できるのか,ちょっと試してみましょう。
分数の計算フォーム
実数にはもう一つ,分数としては表現できない「無理数(irrational number)」があります。有名なものとしては円周率
や平方根
があります。これも実数の一種なので,数直線上の点として実在しています。

有理数(分数)を小数で表現すると,次のように必ず同じ数のパターンが繰り返し出てきます。
\[\begin{split}
\frac{1}{2} &= 0.50000\cdots = 0.5\dot{0} \\
\frac{1}{3} &= 0.33333\cdots = 0.\dot{3}
\end{split}\]
無理数では同じパターンの数の羅列が永久に続くことはありません。ちょっと計算してみましょう。
Try! MPFR
従って,実数を一言で表現すると「循環したりしなかったりする)無限桁の小数として表わすことのできる数」ということになります。何故無限桁が必要になるのかは,微分積分(数学II, III)に登場する「極限」というものと深い関係がありますが,それは大学に入ってからじっくり考えてみて下さい。
現在の科学技術では実数を土台とする計算が不可欠です。そしてほとんどの計算はコンピューターの中で行います。では,次にコンピューターの中身について見ていくことにしましょう。
2.コンピュータの仕組みと「データ」
今のコンピューターの筐体とぱかっと開けると,マザーボードと呼ばれるデジタル回路基板の上に様々な集積回路が載っていることが分かります。下記は本研究室で故障したマシンのマザーボードの写真です。

マザーボードの上に乗っている集積回路のうち,重要なものがCPU(Central Processing Unit)とメモリ(RAM)です。コンピューターで行われる処理は,全てのデータが「数」として表現されてメモリに格納され,処理内容に応じてメモリ内のデータが読み出されて処理され,またメモリに書き戻されます。

このCPUとメモリとのやり取りに要する時間,CPU内の処理(計算)に要する時間が短ければ短いほど,コンピューターの性能が高いと言えます。とても高速な処理が可能になっていますが,困ったことに,メモリの大きさは有限で,処理時間も短いとはいえゼロにはなりません。「無限の長さを持つ実数」をそのままコンピューターのメモリに格納することは不可能ですし,仮にできたとしても,無限の長さの処理時間を要することになります。「無限大(∞)」とは果てのないことを意味し,それ故に完ぺきな数学,特に微分積分のような解析学という体系が成り立つわけですが,これではコンピューターには実数の計算が出来ないことになってしまいます。
3.「誤差」を含まざるを得ない科学技術計算
仕方がないので,コンピューターの中では無限桁の小数を適当な長さに切って,有限桁の小数(=有理数の一種)として扱うことにします。この適当長さに切る操作のことを「丸め(Round-off)」と呼び,丸めに伴って起きる元の正しい値(真値)とのズレを「誤差(error)」と呼びます。
この丸めに伴う誤差を,関数グラフアプリを使ってみていくことにしましょう。
MPFRgraph
コンピューターは計算が高速なので,いくらでも長い桁の計算ができそうですが,長くなればなるほど計算時間はかかるようになりますし,科学技術開発競争を世界中で行っている昨今,あまり誤差ばかり気にして長い桁の計算ばかりすることは難しいのが現状です。従って,最近のAIの中核技術であるDeep Learningではなるべく短い桁の小数で計算しようとしています。
しかし,いくら計算が速くても,途中で誤差が拡大されて,不正確な値しか出てこないようでは困ります。一般には,丸める桁数を長くすれば誤差も小さくできるわけですが,とてもたちの悪い問題があることは良く知られていて,いくら桁を長く取っても足りなくなるということもあります。
5.誤差評価付き計算の例
性質の良くない問題の例として,「複雑系(Chaos system)」というものがあります。一番シンプルなものとして,ロジスティック写像で定義される次のような数列
の生成問題を取り上げましょう。
出発値
として,次の式の右辺を計算して次の
を導出します。
\[ x_{i+1} := 4x_i (1 – x_i) \]
これで実数列
を,下記のCプログラム
|
#include <stdio.h> main() { int i; float x[102]; /* 単精度 */ x[0] = 0.7501; for(i = 0; i <= 100; i++) { x[i+1] = 4 * x[i] * (1 - x[i]); if(i%10 == 0) printf("%5d %25.17e\n", i, x[i]); } } |
を用いてIEEE754単精度(約7桁)及び倍精度(約15桁)で計算した結果は次のようになります。
|
i 単精度 倍精度 0 7.501000e-01 7.50099999999999989e-01 10 8.445168e-01 8.44495953602201199e-01 20 1.229039e-01 1.42939724528399537e-01 30 4.977781e-01 8.54296020314155413e-01 40 5.816439e-01 7.74995884542777125e-01 50 5.580120e-01 7.95132827423636751e-02 60 2.287482e-02 2.73872762849587226e-01 70 9.985481e-01 9.97021556611396687e-01 80 9.421800e-01 3.52785425069070790e-01 90 2.129389e-01 6.35558111134618908e-01 100 7.568640e-01 5.15390006286616020e-01 |
同じ値になる名図ですが,
あたりでもう単精度の値は最初の一桁目しか合っていません。
以降はもう全く違う数です。
このような場合はもっと桁数を増やして計算する必要があります。50桁計算すると下記のようになります。
|
0, 7.501e-01 10, 8.4449595360221744753714870256154137267401952291229e-01 20, 1.4293972451230765528428175723130628307376969200214e-01 30, 8.5429600370442189166613118425888744374797589008230e-01 40, 7.7497575311820124128022346126421168481511313675586e-01 50, 9.3375332197703029055189668015168234762823013462054e-02 60, 4.0822016829087813187102766181952686730359229911213e-01 70, 7.1511999705058574897099346417593218350311240577887e-02 80, 4.6325330290077571055993467458320747053550671068192e-01 90, 1.3344050120868840464692012150021107621136608489291e-03 100, 7.8817989371509906806704770440086762650658767283966e-02 |
100桁計算すると,
|
0, 7.501e-01 10, 8.44495953602217447537148702561541372674019522912291280007848388118986290823419270975121157e-01 20, 1.42939724512307655284281757231306283073769692002141121387637872878254442652405447154653323e-01 30, 8.54296003704421891666131184258887443747975890082277146848716525830873157517999293072280730e-01 40, 7.74975753118201241280223461264211684815113136725464353115716739919423777262939033931884043e-01 50, 9.33753321977030290551896680151682347628230351487453020066411681729685131997586455460349178e-02 60, 4.08220168290878131871027661819526867303629812881299069587191914174238088850476295365965331e-01 70, 7.15119997050585748970993464175932183301720988330772170512558519903814407400108286900737962e-02 80, 4.63253302900775710559934674583207430627755245547675072149154489741355095166291517037937750e-01 90, 1.33440501208688404646920121499911906832767847107910033479094701619779865654038757784875352e-03 100, 7.88179893715099068067047704626992647240094450346038441154434366642728380004689090274960709e-02 |
となり,大分同じ数に近づいてきたことが分かります。
このように,丸めによる誤差の影響が大きく出る「悪条件問題」というものが,科学技術計算にはたまに出てきます。計算途中で誤差がどの程度は行っているのかを自動的に調べる「区間演算(interval arithmetic)」という技術もありますが,このような悪条件問題の場合は大した役には立たず,結局桁数を多くしないとまともな値を得ることはできません。下記の例は,MPFIと呼ばれる多倍長計算を区間演算に利用できるソフトウェアを使ったロジスティック写像の掲載例です。一応,プログラムもつけておきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
$ ./logistic_mpfi prec(bits) = 128 0, [7.5009999999999999e-1,7.5010000000000001e-1] 3.918e-39 10, [8.4449595360221744e-1,8.4449595360221745e-1] 4.865e-33 20, [1.4293972451230765e-1,1.4293972451230766e-1] 3.014e-26 30, [8.5429600370442189e-1,8.5429600370442190e-1] 5.288e-21 40, [7.7497575311819887e-1,7.7497575311820361e-1] 6.112e-15 50, [9.3375329714176199e-2,9.3375334681229861e-2] 5.319e-08 60, [4.0561739523590902e-1,4.1082572849550305e-1] 1.276e-02 70, [-2.9373100713009889e43,2.1377489377594625e43] 5.075e+43 80, [-1.2245050211453045e45127,8.9118419393669329e45126]2.116e+45127 90, [-9.5278547330878072e46210753,6.9342905040200581e46210753]1.646e+46210754 100, [-@Inf@,@Inf@] inf $ ./logistic_mpfi prec(bits) = 256 0, [7.5009999999999999e-1,7.5010000000000001e-1] 1.151e-77 10, [8.4449595360221744e-1,8.4449595360221745e-1] 1.430e-71 20, [1.4293972451230765e-1,1.4293972451230766e-1] 8.857e-65 30, [8.5429600370442189e-1,8.5429600370442190e-1] 1.554e-59 40, [7.7497575311820124e-1,7.7497575311820125e-1] 1.796e-53 50, [9.3375332197703029e-2,9.3375332197703030e-2] 1.563e-46 60, [4.0822016829087813e-1,4.0822016829087814e-1] 3.749e-41 70, [7.1511999705058574e-2,7.1511999705058575e-2] 2.244e-34 80, [4.6325330290077571e-1,4.6325330290077572e-1] 3.633e-29 90, [1.3344050120868840e-3,1.3344050120868841e-3] 1.322e-20 100, [7.8817989371509897e-2,7.8817989371509917e-2] 2.348e-16 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
|
// logistic 写像 // MPFR & MPFI版 #include <stdio.h> #include "mpfr.h" #include "mpfi.h" int main() { int i; unsigned long prec; mpfi_t x[102]; mpfr_t relerr; printf("prec(bits) = "); scanf("%ld", &prec); mpfr_set_default_prec(prec); // 初期化 mpfr_init(relerr); for(i = 0; i < 102; i++) mpfi_init(x[i]); // 初期値 //x[0] = 0.7501; mpfi_set_str(x[0], "0.7501", 10); for(i = 0; i <= 100; i++) { if((i % 10) == 0) { printf("%5d, ", i); mpfi_out_str(stdout, 10, 17, x[i]); mpfi_diam(relerr, x[i]); mpfr_printf("%10.3RNe\n", relerr); } //x[i + 1] = 4 * x[i] * (1 - x[i]); mpfi_ui_sub(x[i + 1], 1UL, x[i]); mpfi_mul(x[i + 1], x[i + 1], x[i]); mpfi_mul_ui(x[i + 1], x[i + 1], 4UL); } // 消去 mpfr_clear(relerr); for(i = 0; i < 102; i++) mpfi_clear(x[i]); return 0; } |
6.まとめ
以上,まとめますと
- 実数は無限桁の小数として表現される数である。
- コンピューターのメモリは有限なので,無限桁の実数を格納することはできず,丸めて短い有限桁に収めなければならない。
- 丸めに伴う誤差が甚大な影響を及ぼす「悪条件問題」というものが存在する。
- 悪条件問題を解決するには桁を増やして対処するしかない。しかしこれは計算時間を要する解決策である。
となります。「高性能計算研究室」ではこのように長い桁の計算,多倍長計算を用いて様々な悪条件問題の解決に取り組んでいます。