ゲームなどを作っていると、乱数が欲しいなと思うことがよくあります。しかし、乱数というのはコンピュータにとっては苦手な分野です。でも、なんとか乱数を使うことができます。今回はその方法を教えましょう。
では、今回の要点です。
では、いってみましょう。
さて、サイコロ2つを振ってその合計を求めたいと思います。そのためには、サイコロの動作を真似する必要があります。
これには、乱数を用いればいいですね。乱数とは、一言で言うと「無茶苦茶な値」です。例えば、まさにサイコロを適当に振ったときの目の値は乱数になりますね。
この乱数は rand という関数で得ることができます。0から RAND_MAX までの範囲の乱数値を得ることができます(RAND_MAX はマクロです)。必要なヘッダファイルは stdio.h です。
しかし、サイコロの目は1〜6です。そこで、rand の値にある計算をほどこして、1〜6の値に変換してしまいましょう。これには普通割り算の余りを使います。6で割った余りは0〜5になります。これに1を足せば1〜6になるという算段です。
まぁ、RAND_MAX + 1 は実は6の倍数ではないので1〜6が完全に均等には現れませんが、RAND_MAX は結構大きいので実際上はあまり気にはなりません。
さて、では次のプログラムを実行してみましょう。
プログラム |
---|
// Rand1.cpp #include <iostream.h> #include <stdio.h> inline int Dice() { return rand() % 6 + 1; } int main() { int i; for(i = 0; i < 20; i++) cout << Dice() + Dice() << ' '; cout << flush; return 0; } |
実行結果例 |
12 10 11 2 8 12 6 8 5 5 5 7 10 7 9 7 7 9 8 3 |
確かに乱数になっていますね。このように、rand を使えば乱数が使えるわけです。
しかし、VC++を使っている人は上と全く同じ結果になっていませんか? VC++以外でも同じになることもあるかも知れません。そして、このプログラムを何回実行しても同じ結果になると思います。
コンピュータは用意された値を正確に計算することは得意なのですが、乱数のように無茶苦茶な値を扱うのには向いていないのです。そこである計算を行い、乱数っぽい値を作ってごまかしているわけです。(しかもC/C++の rand はかなり乱数としての質も低く、乱数としては不自然な挙動を行うことがあります。)なので、コンピュータが計算してはじき出したこの偽物の乱数を疑似乱数と呼びます。
で、やはり疑似乱数が毎回同じ結果をはじくのは困るので、それを防ぐ方法があります。疑似乱数はある初期値を元に計算を行って生成されます。初期値を設定する関数は srand です。この初期値に毎回違う値を設定してやれば、毎回同じ結果をはじくことはなくなります。
しかし、この初期値に毎回違う値を入れるといわれても、「そういうことをするために乱数があるんじゃないのか?」と思うことでしょう。手動で値を入れるのも馬鹿馬鹿しいですよね。そこで、こういうときは普通現在の時間を元に初期化します。現在の時間自体は乱数として利用できませんが、初期値としては十分利用できます。
現在の時間を取得する関数は time です。必要なヘッダファイルは time.h です。この値は「万国標準時(UCT)での1970年1月1日0時0分0秒からの秒単位での経過時間」になります。が、そんなことはどうでもいいです。とにかく、1秒ごとに値が違うということが重要なのです。
この値の型は time_t です。この time_t という型は耳慣れませんが、これもどうでも構いません。これは srand の引数の型である unsigned int に安全にキャストを行うことができます。それさえ分かっていればいいのです。
結局、srand((unsigned int)time(NULL)); という文を書けば、毎回違う結果になるわけです。一応理屈を説明しましたが、実用上はここだけ分かっていればいいです。
で、最終的に、次のようになります。
プログラム |
---|
// Rand1b.cpp #include <iostream.h> #include <stdio.h> #include <time.h> inline void InitRand() { srand((unsigned int)time(NULL)); } inline int Dice() { return rand() % 6 + 1; } int main() { int i; InitRand(); for(i = 0; i < 20; i++) cout << Dice() + Dice() << ' '; cout << flush; return 0; } |
実行結果例1 |
9 8 10 5 6 5 3 4 6 7 6 6 9 4 4 5 8 8 6 7 |
実行結果例2 |
10 11 8 8 9 9 6 7 5 6 4 10 7 9 6 9 4 8 6 12 |
実行結果例3 |
5 2 6 4 2 7 9 9 10 9 2 6 7 8 10 10 9 7 7 11 |
毎回値が変わってくれました。これでやっときちんとした乱数として利用できそうですね。
疑似乱数の生成法にはいろいろあります。rand は線形合同法という方法を使っています。他にもM系列乱数、Mersenne Twister法(MT法)というのが有名です。
MT法は2000年7月29日現在、最も質のよい疑似乱数生成法です。質を求めるなら迷わずMT法を使いましょう。質はMT法よりは落ちますが、速度を求めるならM系列乱数がいいでしょう。線形合同法は質があまり良くないので、普通のゲームなどには利用できますが、数学、物理、化学計算といった、精度の要求される場合には使えません。
この中でMT法は日本人が開発したので、開発者のホームページは日本語です(結構英語のページもありますが)。興味のある人は一度検索してみてはどうでしょうか?
さて、今回の要点です。
次回は面白い型の作り方を紹介します。特定の値しかとることのできない型です。それでは次回まで。
Last update was done on 2000.7.30
この講座の著作権はロベールが保有しています