Lesson 11 乱数を使う

1
Lesson 11 乱数を使う
サイコロを何回も降って、出た目を延々と記録していけば、不規則に並ぶ数字の羅列が得られるだろう。こ
の羅列の中には、1から6までの数字が同じ確率で現れているはずである1 。そのような数字の羅列を、「一
様分布乱数」という。ここでは単に乱数とよぼう。
さて、コンピュータは、人間が指定した計算を素早く行うだけの道具である。何らかの計算手続きによって
生成される数字の羅列には、かならず規則があるはずだ。なので、コンピューターに乱数を生成させるのは
不可能なのである。しかし様々な工夫がなされていて、「見かけ上限りなくデタラメに見える」乱数は生成
することができる。そのような乱数を「疑似乱数」と呼ぶ。
11-1 疑似乱数を使う
実際に乱数を生成するための計算は、なかなか難しい。ここでは出来合いのものをつかって、乱数の応用法
を学ぼう。まず、あらかじめstdlib.hをインクルードしておく。プログラムの冒頭に
#include<stdlib.h>
と書いておこう。あとは、
random()
とかけば、この関数が0からRAND_MAXの間の疑似乱数を返してくれる2 。返り値はint型である。つぎのプログ
ラムを試してみよ。
sample18.c
#include<stdio.h>
#include<stdlib.h>
int main( void )
{
int i;
for(i=1; i<=20; i++){
printf("%d\t",random()); /*"\t"はTAB*/ if( i % 5 == 0){ /*五回ごとに改行*/
printf("\n");
}
}
}
実行結果は以下の通り。
[nagahiro@Tacoma]: ./a.out
1804289383
846930886
424238335
719885386
1025202362
1350490027
1967513926
1365180540
[nagahiro@Tacoma]:
1681692777
1649760492
783368690
1540383426
1714636915
596516649
1102520059
304089172
1957747793
1189641421
2044897763
1303455736
乱数を[0,1]の実数として得たいときは、
1
厳密に言えば、床の上を転がるサイコロの運動は、ある運動方程式を解くことで得られる。なのでサイコロのそれぞれの目が、完全
に問う確率で現れるかどうかは、分からない。
2 RAND_MAXの値はstdlib.hのなかで定義されていて、使用しているコンパイラによって異なる。printfで表示すれば、値を調べられ
る。
2
random()/(1.0*RAND_MAX);
[-0.5,0.5]の実数の乱数がほしいなら
(random()-0.5*RANDMAX)/(1.0*RAND_MAX);
random()/(1.0*RAND_MAX)-0.5
というようにして、状況に応じて工夫して使用する。
種を指定する
上のプログラムは、何回実行しても同じ順番で同じ数字の羅列が生成される3 。このような乱数の発生系列
は、異なる「種(seed)」を指定してあげることで変更できる。random()を呼ぶ前に一度
srandom(seed);
を実行して種を指定する。seedの値は、int型の正の値と決められていて、プログラマーが適当な値を入れて
あげればよい(何も指定しない場合は、自動的に1が種にされる)。次の例では、現在時刻の値を利用し
て、種を指定している(いつも異なる乱数列を得るための、定番的方法)。
sample18.c
#include<stdio.h>
#include<stdlib.h>
#include<time.h>/*関数time()を使うために必要*/
int main( void )
{
int i;
srandom( (unsigned int)time(NULL)); /*種を指定*/
/*符号を含まないint型(unsigned int)に型キャスト*/
for(i=1; i<=5; i++){
printf("%d\t",random());
}
printf("\n");
return 0;
}
実行結果(三回):それぞれ違う乱数列になっていることを確認してほしい。
[nagahiro@Tacoma]: ./a.out
1553507380
1924538679
[nagahiro@Tacoma]: ./a.out
660358393
310491072
[nagahiro@Tacoma]: ./a.out
521573006
1715615616
[nagahiro@Tacoma]:
3
1459691473
45911062
1167576258
1281804812
2131024924
1839954441
1739203232
590563184
204621463
コンピュータはある決められた計算をしているだけのだから、当然であろう。
3
11-2 じゃんけんプログラム
乱数がよく利用される分野の一つに、コンピュータゲームがある。ここでは単純なじゃんけんプログラムを
作ってみよう。コンピュータにじゃんけんをさせる場合は、グーチョキパーをランダムに出させるように
る4 。
n=random()%3
とすると、nには、乱数を3で割った余りが格納され、n=0,1,2のいずれかになる。0,1,2をそれぞれグー
チョキパーに対応させればよい。
【提出課題 1】キーボードからグーチョキパーを入力して、乱数を使ったじゃんけんプログラムを作成せ
よ。
11-3 モンテカルロ法
乱数の重要な応用として、モンテカルロ法がある。モンテカルロはF1で有名なモナコの都市で、同市がカジ
ノで有名なことから、この名が付けられた。最も簡単なモンテカルロ法として、面積の計算をしてみよう。
y
(a,b)
x
O
面積計算の基本的な考え方を述べる。右図のグレーの部分の面積 S を求めたい。そのために、点(a,b)を頂
点とする長方形の範囲内に、ダーツを打ち込む。ダーツがデタラメに投げられるものとすれば、長方形の範
囲内に入るもののうち、グレーの部分にダーツが刺さる確率は、。
p=S/(ab)
であることは直ぐに分かる。乱数を使って、
0<x<a, 0<y<b
の範囲内のx, yの値を取得し、それをダーツの刺さった位置とする。その位置が、図のグレーの部分に入っ
ているかどうかを判定し、この作業を数万回繰り返すことで、確率pを求めることができるだろう。pが分か
れば、上の式からSも分かる寸法だ。
次の例では、f(x)=x2, a=b=1として、面積を計算している。ab=1だから、p=S。つまり、ダーツがグレーの
部分に刺さる確率が、そのまま面積に等しい例である。
4 参考までに:過去のパターンを解析するじゃんけんプログラムがある。http://www-fcs.acs.i.kyoto-u.ac.jp/ aoki/janken/を参照。
殆どの人が勝つことのできないプログラムが公開されている。
4
sample19.c
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
double f(double);
double f( double x ){
return x*x;
}
int main( void )
{
int i;
int sum = 0, n=10000; /*nはダーツを打ち込む回数*/
double x,y;
srandom( (unsigned int)time(NULL));
for(i=0; i<n; i++){
x = random()/(1.0*RAND_MAX);/*[0,1]の乱数を取得*/
y = random()/(1.0*RAND_MAX);
if( y < f(x) ){ /*もし、点(x,y)がf(x)よりも小さかったら*/
sum++; /*グレー部分にダーツが当たったので、その回数を1だけ加算*/
}
}
printf( "p=%lf\n",sum/(1.0*n));
return 0;
}
実行結果は以下の通り。真値は0.3333…なので当たらずとも遠からず。nの値を増やしていろいろ試してみ
よう。
[nagahiro@Tacoma]: ./a.out
p=0.329600
[nagahiro@Tacoma]:
【提出課題 2】sample19.cにて、
f (x) =
!
1 − x2
とすれば、1/4の円の面積が求まる。その結果から円周率πを求めよ。