C言語の乱数(rand)の扱い方【rand_r, srand】
- 作成日: 2024-03-13
- 更新日: 2024-03-13
- カテゴリ: C言語
C言語の乱数の使い方を解説します。
C言語では乱数を得たい時はrand()
を使うのが一般的です。
rand()
は乱数を返す関数です。
srand()
でシード値を初期化できます。
C言語や他の言語を扱うYoutubeも公開しています。
興味がある方は以下のリンクからご覧ください。
乱数とは?
乱数とは乱れた数、つまりランダムな値のことを言います。
乱数はゲーム処理などでよく使われます。
たとえばコイントスゲームでは、コインの裏と表を決める必要があります。
この時に乱数を使い、0から1の範囲の乱数を得て、0だったら裏、1だったら表とします。
他にも乱数はさまざまなケースでよく使われるものです。
開発者として乱数の使い方を覚えておくのは必須と言えるかもしれません。
乱数と乱数列
乱数は取得できる乱数1つのことです。
乱数列とは乱数が並んだ列のことです。
rand()
は乱数を返します。
rand()
が何度も呼ばれてその乱数が連なったものが乱数列です。
rand()の使い方
#include <stdlib.h>
int rand(void);
rand()
は0以上RAND_MAX
以下の範囲の疑似乱数整数を返します。
返り値はint
型で、引数はありません。
ただ呼び出すだけで乱数を得られます。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n = rand();
printf("%d\n", n); // 1804289383
return 0;
}
RAND_MAX
は定数ですがこれはstdlib.h
をインクルードすると使えるようになります。
rand()
の返す値をRAND_MAX
で割ると、値を0
から1
の範囲の実数にできます。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
double n = (double) rand() / RAND_MAX;
printf("%f\n", n); // 0.840188
return 0;
}
rand()
はシンプルな関数ですが、シード値を初期化しないと同じ乱数列を返します。
普通はプログラムを起動するごとに新しい乱数が欲しいところですが、rand()
はプログラムを何回起動しても同じ乱数列を返してきます。
ですのでプログラムの起動ごとに違う乱数を得たい場合はsrand()
でシード値を初期化する必要があります。
srand()でシード値を初期化する
#include <stdlib.h>
void srand(unsigned int seed);
rand()
の返してくる乱数列を変えたい場合は、srand()
でシード値を指定します。
srand()
の引数にunsigned int
型の値を渡すとそれがシード値になります。
srand()
はそのシード値をもとに乱数列を作ります。
srand()
に同じ値を渡した場合は同じ乱数列が生成されます。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
srand(100);
int n = rand();
printf("%d\n", n); // 677741240
return 0;
}
プログラムの起動ごとに違う乱数列が欲しい場合は時間を利用します。
time()
関数を使うとtime_t
型の時間を表す整数が得られます。
この整数をsrand()
に渡すことで時間を利用した乱数列を取得できます。
#include <time.h>
time_t time(time_t *tloc);
time()
は1970年1月1日 00:00:00(UTC)からの秒数を返します。
time_t
型のポインタを引数に渡すとそのポインタにも秒数を格納します。
必要ない場合はNULLを渡します。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
srand(time(NULL));
int n = rand();
printf("%d\n", n); // 798874608
return 0;
}
上記のようにすると、プログラム起動ごとに時間を使った乱数列が得られるようになります。
そのためプログラム起動ごとに違う乱数が得られます。
rand_r()の使い方
#include <stdlib.h>
int rand_r(unsigned int *seedp);
rand_r()
もrand()
と同様に0以上RAND_MAX
以下の範囲の疑似乱数整数を返します。
rand_r()
は引数にシードを指定できます。
指定できるのはunsigned int
型のポインタです。
seedp
の値が同じ時、同じ乱数列が得られます。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
unsigned int seed = time(NULL);
int n = rand_r(&seed);
printf("%d\n", n); // 1365811183
return 0;
}
drand48()の使い方
#include <stdlib.h>
double drand48(void);
drand48()
は線形合同アルゴリズムと48ビット整数演算を使って非負数の疑似乱数を生成します。
返り値はdouble
型です。
drand48()
を使う場合はsrand48()
でシードを初期化した方がいいでしょう。
drand48()
は0.0から1.0の範囲で一様分布する乱数を返します。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
srand48(time(NULL));
double n = drand48();
printf("%f\n", n); // 0.865564
return 0;
}
erand48()の使い方
#include <stdlib.h>
double erand48(unsigned short xsubi[3]);
erand48()
は線形合同アルゴリズムと48ビット整数演算を使って非負数の疑似乱数を生成します。
返り値はdrand48()
と同様にdouble
型です。
引数にunsigned short
型の配列を取ります。
erand48()
はこの配列に48ビット整数値を保存します。
その値の下位16ビットをxsubi[0]
に、中位16ビットをxsubi[1]
に、上位16ビットをxsubi[2]
に保存します。
erand48()
はsrand48()
によるシードの初期化を必要としません。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
unsigned short xsubi[3] = {0};
double n = erand48(xsubi);
printf("%f\n", n); // 0.000000
printf("%d %d %d\n", xsubi[0], xsubi[1], xsubi[2]);
// 11 0 0
n = erand48(xsubi);
printf("%f\n", n); // 0.000985
printf("%d %d %d\n", xsubi[0], xsubi[1], xsubi[2]);
// 59066 37933 64
n = erand48(xsubi);
printf("%f\n", n); // 0.041631
printf("%d %d %d\n", xsubi[0], xsubi[1], xsubi[2]);
// 22845 21582 2728
return 0;
}
erand48()
は引数のxsubi
の値に応じて乱数を生成します。
上記では初期値は0にしていますが、初期値を変えれば違う乱数列を得られます。
lrand48()の使い方
#include <stdlib.h>
long int lrand48(void);
lrand48()
は線形合同アルゴリズムと48ビット整数演算を使って非負数の疑似乱数を生成します。
lrand48()
が返す値はlong int
型で、範囲は0から2^31の範囲です。
lrand48()
を使う場合はsrand48()
でシードを初期化した方がいいでしょう。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
srand48(time(NULL));
long int n = lrand48();
printf("%ld\n", n); // 724101703
return 0;
}
nrand48()の使い方
#include <stdlib.h>
long int nrand48(unsigned short xsubi[3]);
nrand48()
は線形合同アルゴリズムと48ビット整数演算を使って非負数の疑似乱数を生成します。
nrand48()
が返す値はlong int
型で、範囲は0から2^31の範囲です。
引数にunsigned short
型の配列を取ります。
nrand48()
はこの配列に48ビット整数値を保存します。
その値の下位16ビットをxsubi[0]
に、中位16ビットをxsubi[1]
に、上位16ビットをxsubi[2]
に保存します。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
unsigned short xsubi[3] = {0};
long int n = nrand48(xsubi);
printf("%ld\n", n); // 0
printf("%d %d %d\n", xsubi[0], xsubi[1], xsubi[2]);
// 11 0 0
n = nrand48(xsubi);
printf("%ld\n", n); // 2116118
printf("%d %d %d\n", xsubi[0], xsubi[1], xsubi[2]);
// 59066 37933 64
n = nrand48(xsubi);
printf("%ld\n", n); // 89401895
printf("%d %d %d\n", xsubi[0], xsubi[1], xsubi[2]);
// 22845 21582 2728
return 0;
}
nrand48()
は引数のxsubi
の値に応じて乱数を生成します。
上記では初期値は0にしていますが、初期値を変えれば違う乱数列を得られます。
mrand48()の使い方
#include <stdlib.h>
long int mrand48(void);
mrand48()
は線形合同アルゴリズムと48ビット整数演算を使って符号付きの疑似乱数を生成します。
mrand48()
が返す値はlong int
型で、範囲は0から2^31の範囲です。
mrand48()
を使う場合はsrand48()
でシードを初期化した方がいいでしょう。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
srand48(time(NULL));
long int n = mrand48();
printf("%ld\n", n); // -180277453
return 0;
}
jrand48()の使い方
#include <stdlib.h>
long int jrand48(unsigned short xsubi[3]);
jrand48()
は線形合同アルゴリズムと48ビット整数演算を使って符号付きの疑似乱数を生成します。
jrand48()
が返す値はlong int
型で、範囲は0から2^31の範囲で一様分布です。
引数にunsigned short
型の配列を取ります。
jrand48()
はこの配列に48ビット整数値を保存します。
その値の下位16ビットをxsubi[0]
に、中位16ビットをxsubi[1]
に、上位16ビットをxsubi[2]
に保存します。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
unsigned short xsubi[3] = {0};
long int n = jrand48(xsubi);
printf("%ld\n", n); // 0
printf("%d %d %d\n", xsubi[0], xsubi[1], xsubi[2]);
// 11 0 0
n = jrand48(xsubi);
printf("%ld\n", n); // 4232237
printf("%d %d %d\n", xsubi[0], xsubi[1], xsubi[2]);
// 59066 37933 64
n = jrand48(xsubi);
printf("%ld\n", n); // 178803790
printf("%d %d %d\n", xsubi[0], xsubi[1], xsubi[2]);
// 22845 21582 2728
return 0;
}
jrand48()
は引数のxsubi
の値に応じて乱数を生成します。
上記では初期値は0にしていますが、初期値を変えれば違う乱数列を得られます。
srand48()の使い方
#include <stdlib.h>
void srand48(long int seedval);
48系の乱数生成関数はこのsrand48()
でシード値を初期化することができます。
srand()
の時と同じようにtime()
を利用することでプログラム起動ごとに異なった乱数列を得ることができるようになります。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
srand48(time(NULL));
double n = drand48();
printf("%f\n", n); // 0.865564
return 0;
}
srand48()
の引数の値が同じ場合は同じ乱数列を生成します。
rand()を使ったテクニック
ここからはrand()
を使ったテクニックを紹介します。
紹介するのは以下のテクニックです。
- modで範囲内の乱数にする
- 範囲指定の乱数を得る
modで範囲内の乱数にする
rand()
が返す値は大きいため、そのままだと使い勝手が悪いことがあります。
そういう場合は%
演算子で剰余(mod)計算すると値が指定の範囲内に収まります。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n = rand() % 10;
printf("%d\n", n); // 3
n = rand() % 10;
printf("%d\n", n); // 6
n = rand() % 10;
printf("%d\n", n); // 7
return 0;
}
%
演算子は剰余を求める演算子です。
剰余とは割り算で割った余りのことです。
たとえば12を10で割った場合、あまりは2になります。
そのため12 % 10
の結果は2です。
3で剰余を求めた場合は値が0, 1, 2, 0, 1, 2
という感じで循環するようになります。
ですので巨大な乱数を範囲内に収めたい時に%
演算子で剰余すると便利です。
注意点としては%
演算子は割り算の仲間なので処理がけっこう重いというのがあります。
ですのでゲーム開発などでは使いすぎないように注意した方がいいかもしれません。
範囲指定の乱数を得る
ある特定の値の範囲内の乱数を得たい場合は以下の記事を参照してください。
rand()を使ったサンプルゲーム
ここからはrand()
を使ったサンプルゲームを紹介します。
紹介するのは以下のゲームです。
- コイントス
- おみくじ
コイントス
コイントスゲームは裏と表があるコインを投げてどちらが出るか当てるゲームです。
投げたコインの表現にrand() % 2
を使います。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
printf("コインを投げるので裏か表か当ててください。\n");
for (;;) {
char buf[100];
printf("裏(0) 表(1) > ");
fflush(stdout);
if (!fgets(buf, sizeof buf, stdin)) {
break;
}
int n = atoi(buf);
if (n == rand() % 2) {
printf("当たり!\n");
} else {
printf("はずれ!\n");
}
}
return 0;
}
ゲームを起動すると最初にプロンプトが出ます。
そしてユーザーが0
か1
を入力します。
そうするとrand() % 2
でコインを投げて、if文で比較して判定します。
ユーザーもrand() % 2
の結果も0
だったら当たり、0
でなければはずれです。
またユーザーもrand() % 2
の結果も1
だったら当たり、1
でなければはずれです。
おみくじ
おみくじを引くゲームです。
プログラムを起動するごとにおみくじを一枚引けます。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
enum {
N = 3,
};
int main(void) {
srand(time(NULL));
const char *kujis[N] = {
"大吉:今日はついてます。",
"中吉:ぼちぼちです。",
"小吉:がんばっていきましょう。",
};
const char *kuji = kujis[rand() % N];
printf("%s\n", kuji);
return 0;
}
くじは三枚だけです。
くじの枚数はN
で定数にしてあります。
rand() % N
とやるとポインタ配列の添え字が得られます。
これの値を使ってkujis
からくじを一枚引きます。
あとはそのくじを出力して終わりです。