コンピュータ物理学2
第2回(2015.10.9)
第1回 10/ 2(金) ガイダンス第2回 10/ 9(金) 数値表現と誤差第3回 10/16(金) “第4回 10/23(金) 数値微分・積分第5回 10/30(木) “第6回 11/13(金) “第7回 11/20(金) 常微分方程式第8回 11/27(金) “
第9回 12/ 4(金) “第10回 12/11(金) 偏微分方程式第11回 12/18(金) “第12回 12/25(金) “第13回 1/ 8(金) モンテカルロ法第14回 1/ 22(金) 量子力学第15回 1/ 29(金) “第16回 2/ 5(金) “
> Mean value ? : 100
> ./gauss.exe プログラムを実行すると、
平均値、標準偏差、を聞かれるので適当な値を入力すると、
> Standard deviation ? : 30
この値をもとにガウス関数を計算した値のデータファイルが生成される (x=100を中心に±120 の範囲で100個のデータ生成する。刻み幅=240/100=2.4)
‐20.000000 0.000004 ‐17.600000 0.000006 ‐15.200001 0.000008 ‐12.800001 0.000011
210.399841 0.000015 212.799835 0.000011 215.199829 0.000008 217.599823 0.000006
・・・
ファイル
gauss.dat
の中身
第1回の続き; Gauss 分布データの計算・生成達成したい流れ
x
f(x)
こう入力するとm=100, sigma=30をもとにx=‐20 から x=220 まで dx=2.4 刻みでガウス関数値を算出し、出力ファイル (gauss.dat) に書き出すようなプログラムを作る。
> c++ gauss.cc –o gauss.exe ソースプログラムをコンパイルして、
まずどういうプログラムを作りたいのかを想定するのが大事
‐o オプション:コンパイルして出来上がる実行ファイル名を gauss.exe に指定。
/* Gauss function */#include <stdio.h>#include <math.h>#define n 100 /* point number */
double gauss_f(double x, double m, double sigma){double g;g = 1.0/(sqrt(2.0*M_PI*sigma*sigma))*exp(‐(x‐m)*(x‐m)/(2*sigma*sigma));return g;}
int main(){/* local variables */double m, sigma; /* mean, standard deviation */double min, max; /* calculation region */double x, dx, gauss; int i;FILE *outfile;
/* main program */printf("Mean value ? ");scanf("%lf", &m);printf("Standard deviation ? ");scanf("%lf", &sigma);outfile = fopen("gauss.dat", "w");
min = m ‐ sigma*4;max = m + sigma*4;
x = min;dx = (max‐min)/n;for(i=1; i<=n; i++){gauss = gauss_f(x, m, sigma);fprintf(outfile, “%f %11.8f ¥n", x, gauss);x += dx;
}}
プログラムを走らせたら“Mean value?” と端末上に表示させて、そこでキーボードから手入力した数字を変数mに代入する。(sigmaも同様)
scanfで数値を読み取る場合はdouble 型なら “%lf” とする。float 型なら “%f” で良い。
π はヘッダファイル math.hの中で定義されている。日本語変換で “π” とかしないこと。
計算する範囲を最小値、最大値を指定して決定する
x=min から始まって dx ごとに x を増やして100 回関数 gauss を計算する
gauss 関数を計算する部分。ここでは main 関数とは別に用意した関数 gauss にて計算をさせる。渡す変数は計算に必要な x, m, sigma の3つ。
計算刻み幅 dx は全データが 100個になるように (max – min) を 100 で割る
ファイルに出力するのは x と計算値 gauss。浮動小数点表示は %f だが、表示させる桁を指定したい場合は %11.8 等とする(小数点部 8桁、全部で 11桁で揃える場合)
使う変数の型宣言。プログラムでは日本語変換された文字は使わないこと。”σ” ではなく、半角文字の ”sigma” を使う、等。
・後で見て分かりやすいようにコメント文を付けること。
・プログラムを見やすくするために、インデント(字下げ)を活用する。
ここでは分割数はdefine で定義
1回ループごとに x にdx を足す。x = x+dx と書いても良い。
gauss.dat という名前でファイルを作り、書き込めるモード “w” で開く。
#include <iostream>#include <fstream>#include <math.h>
using namespace std;
double gauss_f(double x, double m, double sigma){double g;g = 1.0/(sqrt(2.0*M_PI*sigma*sigma))*exp(‐(x‐m)*(x‐m)/(2*sigma*sigma));return g;}
int main(){
double m, sigma, min, max, gauss, x, dx;int i, n=100;
cout << "Mean value ?";cin >> m;
cout << "Standard deviation ?";cin >> sigma;
std::ofstream ofs("gauss.dat");
cout << "m=" << m << endl;cout << "sigma=" << sigma << endl;
min = m‐sigma*4;max = m+sigma*4;
x = min;dx = (max‐min)/n;for(i=1; i<=n; i++){gauss = gauss_f(x, m, sigma);ofs << x << " " << gauss << std::endl;x+=dx;}
}
C++ 仕様の書き方:
gnuplotで ‘gauss.dat’ をプロットする
(1) スタートメニューから gnuplotを起動する(2) 現在の作業ディレクトリに移動する
・ gnuplotでは unix コマンド “cd” (change directory) が使えるただし、移動先のディレクトリ名は “ “ で囲む。
・ この授業での環境なら cd “z:” 等。(3) gauss.dat をプロットする
plot “gauss.dat”と入力するとデータの 1列目を横軸、2列目を縦軸にしてプロットしてくれる
(4) もうひとつ作った gauss0.dat も並べてプロットする。
第2-3回のテーマ
・ 扱える数値の大きさ・小ささ・ 数値計算で生じる誤差
例題・レポート問題を通してこれらを定量的に評価できるようにする。
・級数和の計算・計算結果ファイルから数値を読み取っての比較
数値表現
・ bit : メモリの最小記憶単位 (0, 1 の2進数)
・ N bit を使って表せる整数は 2N 個→ 符号の正負区別に 1bit 使うので 2N‐1 個
・ byte : 1 byte = 1 B = 8 bits1 kB = 210 B = 1024 bytes
1 byte で英数字1文字を記憶できる
1 B
int型整数: 32 bit
231 までの整数を表現できる 6482,147,483,231
“a” = 0x61 = 01100001“b” = 0x62 = 01100010 等
許される限度を超えた大きな数 → overflow計算機が扱える精度より小さな数 → underflow
1byte = 8bit は 28 =256
limits.h 内でINT_MAX 等で定義されている
浮動小数点は?
(-1)0 x 0.1234567 x 102
float 型 1つの実数変数を表すのに 32 bit を使う
1bit: 符号部 8bit: 指数部 23bit:仮数部
仮数部の有効桁数 = 2進数で 23桁= 10進数で log 223 ≈ 7 桁
x = (-1)[符号部] × [仮数部] × 2[指数部]‐[バイアス]
[バイアス]=127
2‐126 ~ 2127 を実現→10進数で 38桁
2323
33
22
11 2222 mmmm
例:
例: 3.5
仮数: 符号 = +, 絶対値 = 2.5 (仮数部の定義は [仮数部 ‐ 1])基数: 2 で固定 (IEEE方式)指数: 0
x = (-1)0 × 3.5 × 20 = (-1)0 ×1.75× 21 = (-1)0×(1+0.75) × 2128‐127
符号部 = 0仮数部 = 0.75 = 0.11000000000000000000000指数部 = 128 = 27 = 10000000
double 型 1つの実数変数を表すのに 64 bit を使う(倍精度)
指数部 11 bit
仮数部の有効桁数 = 2進数で 52桁= 10進数で log 252 ≈ 16 桁
5252
33
22
11 2222 mmmm [バイアス]=1023
2‐1022 ~ 21023 を実現→10進数で 308桁
符号部 1bit 仮数部 52 bit
例題1:数値範囲の確認
double 型の変数 xに対して1~180 までの階乗を計算し、コンピュータ上での数値を確認せよ。
!180,......,!5,!4,!3,!2,!1xまた、各時点の x と同時に y=1/x と z=x*y も表示して、計算結果がどう表示されるか確認せよ。
i=1 ; x=1.0 , y=1.0, z=1.0i=2 ; x=2.0 , y=0.5, z=1.0i=3 ; x=6.0 , y=0.166667 , z=1.0: : : :: : : :
※階乗は for ループの各回ごとに 1づつ増える整数をかければ良い。 x*=i 等( I はfor文のパラメータ)。大きな数値や小さな数値を表示させるときは指数で出すのが良い; printf(“%e”, x)。
無限大 (inf)や非数値 (NaN) がどういう状況で出てきてしまうかわかっておく。(今後も計算結果に inf / NaNが出て悩むことがあるかもしれないので)
端末上にこう出力させたい
i=1 ; x=1.0 , y=1.0, z=1.0i=2 ; x=2.0 , y=0.5, z=1.0i=3 ; x=6.0 , y=0.166667 , z=1.0: : : :: : : :
端末上にこう出力させたい
For 文 for(i=1; i<=180; i++) を使ってi=1 の時点では x = 1i=2 の時点では x = 2×1i=3 の時点では x = 3×2×1等々
を各回ごとに出力する。
1回前のループでの x の値に今回のループでのiの値をかけることで、階乗が得られるx = x * i(x に i をかけた値を新たに x に代入する)これと同じ演算を x *= i と書ける。
そのためには:
x=1.0;for(i=1; i<=n; i++){x *= i;y=1/x;z=x*y;printf("i=%d x=%e y=%e z=%e¥n", i, x, y, z);
}
当然、iが増えていくと、x! は爆発的に増えていく。そこで浮動小数点が扱える範囲を超えるのを確認する。
同時に x の逆数も各ループで見てみる。y=1/x として。こちらは iが増えていくと、極めて小さい数値になっていく。
x*y は定義上 1 だが、x や y が正常値をとれなくなると、値が定義できなくなるので、それも見る。
浮動小数点を指数型(ax10^b) で表示させると大きな数値や小さな数値を精度良く見れる。(%e)
0! = 1 なので、初期値はx=1.
数値計算における誤差
(1) 近似誤差
(2) 丸め誤差
コンピュータで問題を解くために、数学の厳密性を簡単化したために生じる誤差
・無限級数 → 有限の和・無限小の間隔 → 有限のステップ
N
n
n
n
nx
nx
nxe
00 !!
N
n
N
nN NNnf
NNnfdxxf
00
1
0
11lim)(
計算機で扱う数値は有限個のビットで表されるので(有限な桁数を扱う)精度に限界があり、誤差が生じる。
計算内容によっては有限桁数しか扱えないために生じる誤差が蓄積して大きな誤差を生じることがある。
・引き算で生じる誤差 (桁落ち)・掛け算で生じる誤差
コンピュータ上で計算を行う際、2種類の誤差を考慮する必要がある。
演算で生じる誤差
cba 2つの数値の和を計算する場合を考える:
計算機の中での数値は添え字c を付けて厳密値と異なる近似値だと考えられる: ccc cba
cbcbc cbcbcba 11
計算機内の有限桁数により生じる誤差を εb, εc とすると
cbc
ac
ab
aa 1
cb cb
→ 相対誤差は
だとすると(一方の誤差が大きい場合) bc
ab
aa 1
誤差 1% なら ε = 0.01誤差 0.1%なら ε = 0.001
a の平均的誤差は b と c の誤差を重み付きで平均したもの
大きい方の誤差で和の誤差が決まる
計算機で行われる計算は解析的な解に対する近似でしかない。
cba ccc cba 一方、引算では
ac
ab
aa
cbc 1→ 引算で生じる相対誤差は
b ≈ c のときは a の相対誤差が大きくなる
ほとんど同じ大きさの数の間で差をとる→ 大きい桁の部分がなくなる→ 残りの小さい桁の部分に占める誤差の割合が相対的に大きくなる
桁落ち
桁落ちの例
考えるの差を計算することをと 71068.70500071775.705001
ともに有効数字 7 桁の精度を保持している
00707.071068.7071775.7050005001
有効数字は3桁
考えるの差を計算することをと 071068.750141428.751
ともに有効数字 7 桁の精度を保持している
070360.0071068.7141428.75051
有効数字は5桁
・大きい値同士の差で小さい値が生じるとき
・小さい値同士の差で小さい値が生じるとき
例題2:桁落ちが見られる関数計算 –球 Bessel 関数 ‐
)()(12)( 11 xjxjx
lxj lll
球 Bessel 関数 jl(x) を l = 2,3,4,・・・, 10 まで計算・ファイル出力してグラフで確認する・ 漸化式と既知の j0(x), j1(x) から求める
・ キーボード入力で計算したい次数 l を決めて計算できるようにする・ x = 0.1 (=xmin) から始めて、刻み幅 0.1 で x = 30 (=xmax) までの範囲で計算する・ for文で x を増加するループを作り、各回において決めた l に対する jl(x) を漸化式から計算する。漸化式を計算するのも欲しい l に達するまでfor 文でループさせる。
・ 漸化式計算は関数として別たてするとよいdouble up(double x, int l){}main 関数内でfor(x=min; x<=max; x+=dx){
u = up(x, l);fprintf(outfile, "%f %f ¥n", x, u);
}(上昇漸化式なので関数名を up としてる。)
・ 漸化式は前の2つの項から求めるので、2つ前の項を one1つ前の項を two として、 three = (2.0*n+1.0)/x * two ‐ oneとして新たな項を求める(n を増加させていく)。
・ l が大きくなってくると原点付近で計算に誤差が生じてくる。
210cossin)(,sin)(
xxxxxj
xxxj
j0(x)
j1(x)
x
x
これを計算する(j2(x))同様に j10(x) まで。