MMGamesロゴ  MMGames
Twitterシェアボタン  Facebookシェアボタン   
しんで覚えるC言語
しんで覚えるC言語

乱数

疑似乱数
乱数とは、その名の通りランダムな数のことです。
要するに、サイコロと同じだと考えてください。

ランダムな数が必要になるゲームでは乱数は欠かせません。
また、複雑な現象や統計的な性質の解析などを行う場合には、
乱数を使うことで手軽に実験を行うことができます。

しかし、皆さんもご存じのように、コンピュータは非常に正確な機械であり、
本質的にはランダムに数を作るということはできません。
そこで、計算によってランダムな数を得る、疑似乱数という手法が使われます。

キーワード
【疑似乱数】

計算によってランダムな数値を得る方法。
本当のランダムではないが、現実的にはランダムだと考えて良い。


疑似乱数では、あくまでも計算によってランダムに見える数を作っています。
しかし、実際にかなりバラバラな数値を得ることができるので、
ほぼランダムな数であると考えて良いと思います。

疑似乱数の計算
疑似乱数にはさまざまな計算方法があるのですが、
C言語で用意されるのはほとんどが線形合同法です。

詳しい説明は省きますが、簡単に説明すれば、
X = 適当な数 * X(上位ケタの部分を切り捨てて増加を防ぐ)
をひたすら繰り返すことで毎回異なる値を得る計算です。

この方法は単純ですがそれほどランダムにはならず、
何回か組み合わせて使うと同じパターンになってしまいます。
しかし、ゲームなどの用途であれば十分ランダムな値になります。


乱数を作る
疑似乱数がなんなのかがだいたいわかったところで、
さっそく疑似乱数を使ってみたいと思います。
C言語には、疑似乱数を作るrand関数が用意されています。
なお、rand関数を使うには <stdlib.h> を #include する必要があります。

rand関数
変数 = rand();

rand関数には、とくにパラメータなどを渡す必要はありません。
そのまま使うだけでランダムな値を計算します。
次のプログラムは、乱数を10回計算させた例です。

ソースコード
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int i;

    for (i = 0; i < 10; i++) {
        printf("%d\n", rand());
    }

    return 0;
}

このプログラムの実行結果は、次の通りになるかもしれません。

実行結果
130
10982
1090
11656
7117
17595
6415
22948
31126
9004

見ての通り、ランダムな値が得られています。
乱数の範囲を限定する
前項で、乱数を計算する方法はわかりましたが、
これは数があまりにもバラバラ過ぎて使いにくくなっています。
サイコロの1~6のように、ある範囲の乱数を得ることはできないのでしょうか。

もし、得られる値の最大値がわかるなら、それを等分してやればよいことになります。
C言語では、rand関数で得られる最大値は RAND_MAX という定数の値でわかります。

したがって、rand関数で得られた値をRAND_MAXを等分した値で割れば良いわけですが、
そのための式を計算するのは結構面倒なので、公式を紹介してしまいます。

範囲乱数公式
最小値 + (int)( rand() * (最大値 - 最小値 + 1.0) / (1.0 + RAND_MAX) )

この公式の意味はわからなくてかまいません。
とにかく、この通りにすれば最小値~最大値の範囲の乱数を計算できます。
次のプログラムは、上の公式を計算するGetRandom関数を作り、
GetRandom関数を使用してサイコロを10回振る例です。

ソースコード
#include <stdio.h>
#include <stdlib.h>

int GetRandom(int min, int max);

int main(void)
{
    int i;

    for (i = 0; i < 10; i++) {
        printf("%d\n", GetRandom(1, 6));
    }

    return 0;
}

int GetRandom(int min, int max)
{
    return min + (int)(rand() * (max - min + 1.0) / (1.0 + RAND_MAX));
}

このプログラムの実行結果は、次の通りになるかもしれません。

実行結果
1
3
1
3
2
4
2
5
6
2

見ての通り、1~6の範囲のランダムな数が得られています。

数学とコンピュータ
一般にはコンピュータを使うには数学が重要だと思われています。
本質的にはその通りなのですが、そうとも言えない部分があります。
なぜなら、コンピュータを使えば計算は自動的にやってくれるわけで、
公式さえ知っていれば、それを当てはめるだけで済んでしまうからです。


毎回異なる乱数にする
前項で作ったGetRandom関数を使えば、好きな乱数を計算できます。
しかし、実はまだ、考えなければならない問題が残されています。

初めに説明したように、疑似乱数は計算によるものです。
つまり、同じ数を元に作った場合は同じ乱数になってしまうのです。

このことを確認するために、前項で作成したプログラムを2回実行してみます。

実行結果 1回目
1
3
1
3
2
4
2
5
6
2


実行結果 2回目
1
3
1
3
2
4
2
5
6
2

なんと、1回目と2回目でまったく同じ値が得られています。何回やっても同じです。
これでは、とてもではありませんがサイコロの代わりにはなりません。

この問題を解決するためには、乱数の計算に使う元の数を変える必要があります。
そのための関数として、srand関数が用意されています。

srand関数
srand(元の数);

ただし、srand関数を使って別の数値を入れたとしても、
実行される時に元の数が同じであれば同じ乱数になるので解決にはなりません。
もちろん、srand関数にrand関数を入れても、初めに作られる乱数が同じなので無意味です。
ユーザーに入力させる方法もありますが、手間がかかる不親切な手段です。

要するに、srand関数になんとかして完全にデタラメな数を入れたいのですが、
それにピッタリの方法が一つあります。それは、現在時刻を入れる方法です。
秒単位の現在時刻をsrand関数に入れれば、毎回異なる元の数を乱数に使えます。

現在時刻を得る関数はtime関数で <time.h> を #include する必要があります。
srand関数とtime関数を次のように使えば、毎回異なる乱数を計算できます。

毎回異なる乱数を計算
srand((unsigned int)time(NULL));

乱数発生の場合はtime関数の使い方は知らなくてかまわないのでこの通りにしてください。
なお、unsigned int という型にキャストしていますが、これは符号なしの整数値です。
この処理はプログラムを開始するときに1回行えば十分です。
次のプログラムは、上記の処理を加えて毎回異なる乱数を計算する例です。

毎回異なる乱数を計算
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int GetRandom(int min, int max);

int main(void)
{
    int i;

    srand((unsigned int)time(NULL));

    for (i = 0; i < 10; i++) {
        printf("%d\n", GetRandom(1, 6));
    }
    return 0;
}

int GetRandom(int min, int max)
{
    return min + (int)(rand() * (max - min + 1.0) / (1.0 + RAND_MAX));
}

このプログラムを2回実行した結果は次の通りになるかも知れません。 ```

実行結果 1回目
6
6
5
4
6
5
1
3
2
6


実行結果 2回目
5
2
2
4
3
5
4
1
3
1

見事に異なる値が得られるのがわかります。 なお、ここではmain関数でsrand関数を使用していますが、
次のようにすればこれをGetRandom関数に含めることができます。

毎回異なる乱数を計算
int GetRandom(int min, int max)
{
    static int flag;

    if (flag == 0) {
        srand((unsigned int)time(NULL));
        flag = 1;
    }

    return min + (int)(rand() * (max - min + 1.0) / (1.0 + RAND_MAX));
}

static変数を使うことで、初めの一回だけsrand関数を使うようにしています。


本サイトについて

苦しんで覚えるC言語(苦C)は
C言語入門サイトの決定版です。
C言語の基本機能を体系立てて解説しており、
市販書籍と同等以上の完成度です。

第0部:プログラム概要編
  1. プログラムとは何か?
2章:プログラムの書き方
  1. 書き方のルール
  2. 書き方の慣習
  3. 練習問題2
3章:画面への表示
  1. 文字列の表示
  2. 改行文字
  3. 練習問題3
6章:キーボードからの入力
  1. 入力用の関数
  2. 入力の恐怖
  3. 練習問題6
9章:回数が決まっている繰り返し
  1. 繰り返しを行う文
  2. ループ動作の仕組み
  3. 練習問題9
10章:回数がわからない繰り返し
  1. 回数不明ループ
  2. 入力チェック
  3. 練習問題10
13章:複数の変数を一括して扱う
  1. 複数の変数をまとめて扱う
  2. 配列の使い方
  3. 練習問題13
20章:複数のソースファイル
  1. 最小限の分割
  2. 分割の定石
  3. 練習問題20

コメント
COMMENT

💬 コメント投稿欄を開く