C言語で乱数を扱う方法(rand関数とsrand関数)

C言語で乱数を扱う方法の解説ページアイキャッチ

このページではC言語で乱数を扱う方法を解説していきたいと思います。

乱数を扱うことで、プログラム内でランダムな処理(不確定な処理)を行うことができるようになります。

特にゲームなんかを作ろうと思うとランダムな処理は必須になります。例えば下記のようなところでランダムな処理が用いられています。

  • RPG ゲームで出現する敵をランダムに決定する
  • ジャンケンゲームでコンピュータが出す手をランダムに決定する
  • すごろくゲームでサイコロの出目をランダムに決定する

出現する敵やコンピュータが出す手が決まっていたら面白く無いもんね!

ゲーム好きだから乱数に興味出てきたよ!

プログラムのテストなんかでも使えるよ!

ランダムな値を関数の引数に指定してきちんと動作しているかをテストするとか

他にも乱数はパスワードの自動生成なんかでも使用されます。

いろいろ使いどころは多いですので、是非この機会にC言語での乱数の扱い方について学んで行ってください!

MEMO

乱数とは「法則性のない数値」です

一方、コンピュータやプログラミングで扱う乱数は法則性に則って生成される数値です

これらの乱数は「擬似乱数」と呼ばれます

今回扱う乱数ももちろん「擬似乱数」になります

乱数を生成する(rand 関数)

では乱数を生成する方法について解説していきます。

rand 関数

C言語で乱数を生成するためには rand 関数を使用します。

rand 関数の定義は下記のようになります。

rand関数
#include <stdlib.h>

int rand(void);

rand 関数はC言語の標準関数であり、利用するためには stdlib.h を include する必要があります。

rand 関数を実行すれば、生成された乱数を返却値として1つ取得することができます。

例えば下記のように rand 関数を実行すれば「乱数を1つ取得して表示する」が10回繰り返されることになります。

rand関数の使い方
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int i;

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

    return 0;
}

実行すると私の場合は下記のように表示されました。ランダムな数字が10個生成されていることが確認できると思います。

16807
282475249
1622650073
984943658
1144108930
470211272
101027544
1457850878
1458777923
2007237709

rand 関数で生成される乱数は 0RAND_MAX の範囲の値となります。

RAND_MAX は stdlib.h を include すれば使えるマクロ値です(環境によって定義値は異なると思います)。

スポンサーリンク

特定の範囲の乱数を生成する

次は特定の範囲の乱数を生成するためのテクニックを紹介していきます。

でも僕が作りたいのはサイコロゲームなんだよね…

生成される乱数の範囲は 1 から 6 で十分なんだけど…

rand 関数で生成される乱数の範囲は前述の通り 0RAND_MAX の範囲なんだ

だけど、生成された乱数に対して「剰余演算」や「その他四則演算」を行うことで、特定の範囲の乱数のみを扱うことができるよ!

rand 関数で生成される乱数は前述の通り 0RAND_MAX の範囲です。

ですが、特定の範囲の乱数のみを扱いたいケースは多々あります。

例えばジャンケンゲームであればグーを表す 0、チョキを表す 1、パーを表す 202 の範囲の乱数のみを扱いたいですし、サイコロゲームであれば 16 の範囲の乱数を扱いたいですよね?

こういった特定の範囲の乱数のみを扱いたい場合は「rand 関数で生成された乱数に対して演算を行う」ことが有効です。

生成する乱数のパターン数を絞る

一番使うのが剰余算(%)で乱数のパターン数を絞る方法です。

剰余算は除数で割った時の余りを計算することができます。

ですので、rand 関数で生成した乱数に対して除数で剰余算を行うことで、rand 関数で生成した乱数を 0除数 - 1 の範囲の値に変換することができます。つまり扱う乱数のパターン数を絞ることができます。

例えば 02 の範囲の乱数のみを扱いたいのであれば下記のように演算を行えば良いです。

乱数の剰余演算
int x;
x = rand() % 3;

これにより x に格納される値は必ず 02 の値になります。乱数を剰余算しているだけなので、02 の値もランダムな値となります。

こんな感じで剰余算を利用することで扱う乱数のパターン数を絞ることができます。

特定の範囲のみの乱数を生成する

剰余算で乱数のパターン数を絞った上で、足し算を行うことで特定の範囲の乱数のみを扱うことができます。

例えばサイコロのように16 の乱数のみを扱いたい場合は、rand 関数で生成した乱数をまず 6 を除数として剰余算を計算します。

扱う乱数のパターンを絞る
int x, y;
x = rand() % 6;

これにより扱う乱数(上記の例では x に格納される値)が 05 の6パターンに絞られます。

さらに上記に対して + 1 を行えば、扱う乱数が 1 〜 6 のみになります。

例えば下記のように記述すれば、y には 1 〜 6 の乱数が格納されることになります。

特定の範囲のみの乱数を扱う
int x, y;
x = rand() % 6;
y = x + 1;

応用例1(奇数のみの乱数を生成する)

こんな感じで rand 関数で生成した乱数に対して演算を行うことで、扱いたい範囲の乱数のみを扱うことができるようになります。

例えば下記のように演算を行えば、0NUM 未満の奇数の整数のみを乱数として扱うことができます。

これは 2 をかけた整数に +1 した結果は必ず奇数になるためです。

奇数の整数のみを乱数として扱う
int x;
x = 2 * (rand() % (NUM / 2)) + 1

応用例2(小数の乱数のみを生成する)

また、下記のように演算を行えば、01 の小数を乱数として扱うことができます。

rand 関数で生成される乱数は 0RAND_MAX なので、生成された乱数を RAND_MAX で割ってやれば 01 の小数の乱数になります(キャスト (double) が必要なところに注意です)。

0〜1の小数を乱数として扱う
double x;
x = (double)rand() / RAND_MAX;

応用例3(文字をランダムに生成する)

また、下記のように演算を行えば AZ の文字(英字の大文字)をランダムに生成することができます。

A〜Zの文字をランダムに生成する
char c;
c = rand() % 26 + 'A';

アルファベットは 26 文字なので、剰余算により 025 の乱数にパターンを絞り、それに 'A' を足すことで AZ の文字に置き換えています。

同様に az の文字(英字の小文字)をランダムに生成するためには下記のように演算を行えば良いです。

a〜zの文字をランダムに生成する
char c;
c = rand() % 26 + 'a';

rand 関数の注意点

rand 関数はプログラム内で実行するたびに新たに乱数を生成することができます。

この乱数を生成する時の動作は、イメージ的には下記のように予め作成された乱数の配列から rand 関数を実行するたびに前方から乱数を順番に取得する感じです。

rand関数の実行イメージ

この乱数の並びを「乱数の発生系列」と呼びます。

ここで注意点です。

実は rand 関数を実行するだけだと、プログラム内で生成される「乱数の発生系列」は毎回同じものになってしまいます!!

なので、rand 関数を実行するたびに毎回乱数を生成することができますが、一度プログラムを終了して再度実行した場合は再度同じ乱数の発生系列から乱数が取得されることになります。

毎回同じ順序で乱数が生成される様子

つまり、同じ順序で乱数が生成されることになってしまいます。

例えば前述でも紹介した下記プログラムで10回 rand 関数で生成した乱数を表示すると、

rand関数の使い方
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int i;

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

    return 0;
}

1度目のプログラム実行で表示される乱数が例えば下記のような並びであった時、

16807
282475249
1622650073
984943658
1144108930
470211272
101027544
1457850878
1458777923
2007237709

2度目以降のプログラム実行で表示される乱数も同じに並びになります。

16807
282475249
1622650073
984943658
1144108930
470211272
101027544
1457850878
1458777923
2007237709

例えばジャンケンゲームであれば、対戦相手のコンピュータが毎回同じ順序でグーチョキパーの手を選ぶことになります。

ダメじゃん…
ここがC言語で乱数を扱う上での一番の罠なんだよね…

ですが安心してください!

次に説明する srand 関数を利用することで、「乱数の発生系列」を変更することができます。

ただし、Java や Python 等ではこのような srand 関数を利用することなく、プログラムを実行するたびに乱数の発生系列が自動的に変わるようになっています。

一方でC言語の場合は srand 関数を実行しないと毎回同じ乱数の発生系列になってしまいます。この辺りが上記のような言語と異なりますので、特に他の言語を学ばれた後にC言語で乱数を使用する場合などは注意が必要です。

乱数の発生系列を変更する(srand 関数)

続いて乱数の発生系列を変更する srand 関数について解説していきます。

スポンサーリンク

srand 関数

C言語で乱数の発生系列を変更するためには srand 関数を使用します。

srand 関数の定義は下記のようになります。

srand関数
#include <stdlib.h>

void srand(unsigned int seed);

srand 関数はC言語の標準関数であり、利用するためには stdlib.h を include する必要があります。

引数の seed はまさに「乱数の発生系列」を生み出す「種」になります。

この seed の値を変更してやることで(異なる種を植えることで)、srand 関数実行時に異なる「乱数の発生系列」を生み出す(利用する)ことができます。

逆に同じ seed を設定すると、同じ「乱数の発生系列」が生み出されることになります。

つまり、プログラム実行毎に異なる seed を設定して srand 関数を実行してやれば、毎回異なる「乱数の発生系列」が生成されます。

したがって、その後 rand 関数で乱数を生成すれば、取得先の乱数の発生系列が異なることになるため、毎回同じ順序の乱数が生成されることを防ぐことができます。

毎回異なる順序で乱数が生成される様子

例えば下記の例などは分かりやすいと思います。

srand関数の使い方
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int i;
    unsigned int seed;

    printf("seed = ");
    scanf("%u", &seed);

    srand(seed);

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

    return 0;
}

scanf 関数で指定した値によって異なる乱数の発生系列から10個分の乱数が表示される様子が確認できると思います。

同じ値を指定した場合は毎回同じ結果が表示されることになります。

環境によってはもしかしたら異なるかもしれませんが、srand 関数を実行せずに rand 関数を実行した場合は、srand(1) を実行した時に生成される乱数の発生系列に基づいて乱数が生成されるようです。

srand 関数は「乱数の発生系列」そのものを変更する関数ですので、基本的にはプログラム起動時に一度実行するだけで良いです。rand 関数実行するたびに srand 関数も実行する必要があるというわけではありません。

seed(種)の設定方法

ここまでの解説の通り、srand 関数に設定する seed を毎回異なる値に設定してやれば、毎回異なる乱数の発生系列より乱数を生成することができるようになります。

ではどのようにして srand 関数に設定する seed を毎回異なる値に設定すれば良いでしょうか?次はここについて解説していきたいと思います。

簡単じゃん!

seed も乱数で設定しちゃえばいい!

でも毎回異なる乱数を生成するためには毎回 seed を異なる値に設定して srand 関数を実行してやる必要があるよ?

この seed はどうやって設定するんだい?

ぐぬぬ…

確かに…

seed を毎回異なる値に設定するために、seedrand 関数で生成した乱数に設定してやるのが一番簡単な考え方だと思います。

ですが、毎回 rand 関数で異なる乱数を生成するためには、rand 関数を実行する前に srand 関数に毎回異なる seed を設定して実行してやる必要があります。つまり rand 関数で毎回異なるために seed を設定するのに、また別の seed が必要になるということです。

こう考えると、rand 関数で生成した乱数により毎回異なる seed を設定するのは無理そうだとすぐにイメージがつくと思います。

なので別の手段で seed を設定する必要があります。

この seed の設定に一番良く使用されるのが「時刻」です。

時刻は瞬間瞬間で変化しますよね?なので、この時刻の情報を seed に設定してやれば、毎回異なる seed を設定することができ、毎回異なる乱数の発生系列を理論的には生み出すことができることになります。

で、この時刻を取得するのに使用されるのが time 関数です。

time 関数では(処理系によって異なる場合もありますが)、1970/1/1 00:00:00(GMT) から time 関数実行した瞬間までの経過時間を返却することができます。

time 関数の定義は下記のようになります。利用するためには time.h を include する必要があります。

time関数
#include <time.h>
time_t time(time_t *timer);

time_t 型は(これも処理系により異なるかもしれませんが)、一般に long 型を typedef して名前を再定義したものになります。つまり単なる数値ですね。

ですので、time 関数の返却値を seed として srand 関数に指定してやれば、毎回異なる乱数の発生系列を生成することができます。下記はその例です。

時刻によるseedの設定
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
    int i;
    unsigned int seed;

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

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

    return 0;
}

ただし、まだ注意点があります。それは「time 関数の返却値は秒単位」であることです。

つまり「同じ秒に実行された場合は同じ乱数の発生系列」になってしまうということです。これは覚えておいた方が良い事実だと思います。

しかし、趣味や授業の範囲では上記のような time 関数で seed を設定して乱数を生成するので十分だと思います(正直これ以上 seed を細かく設定する方法を私も知りません…)。

まとめ

このページではC言語で乱数を扱う方法について解説しました。

要点をまとめておくと下記になります。

  • rand 関数を実行することで乱数を生成することが可能
  • 特定の範囲のみの乱数を扱いたい場合は rand 関数の返却値に対して剰余算等の演算を行えば良い
  • srand 関数を実行することで乱数の発生系列を変更することが可能
  • srand 関数の引数 seedtime 関数の返却値を設定することで、毎回異なる乱数の発生系列を利用ことが可能

特にC言語においては毎回異なる乱数の発生系列を利用するのに srand 関数の利用が必要なので注意しましょう!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です