ユーニックス総合研究所

  • home
  • archives
  • c-ransu

C言語の乱数(rand)の扱い方【rand_r, srand】

  • 作成日: 2024-03-13
  • 更新日: 2024-03-13
  • カテゴリ: C言語

C言語の乱数の使い方を解説します。
C言語では乱数を得たい時はrand()を使うのが一般的です。

rand()は乱数を返す関数です。
srand()でシード値を初期化できます。

C言語や他の言語を扱うYoutubeも公開しています。
興味がある方は以下のリンクからご覧ください。

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という感じで循環するようになります。
ですので巨大な乱数を範囲内に収めたい時に%演算子で剰余すると便利です。

注意点としては%演算子は割り算の仲間なので処理がけっこう重いというのがあります。
ですのでゲーム開発などでは使いすぎないように注意した方がいいかもしれません。

範囲指定の乱数を得る

ある特定の値の範囲内の乱数を得たい場合は以下の記事を参照してください。

C言語で乱数を範囲指定して取得する関数を作る

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;  
}  

ゲームを起動すると最初にプロンプトが出ます。
そしてユーザーが01を入力します。
そうすると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からくじを一枚引きます。
あとはそのくじを出力して終わりです。