閉じる

C言語のrand関数の使い方完全ガイド【範囲指定・初期化まで】

プログラミングをしていると、サイコロの目を作ったり、敵の出現タイミングをランダムにしたりと、乱数が欲しくなる場面はとても多いです。

C言語では標準ライブラリのrand関数を使うことで、簡単に乱数を扱うことができます。

しかし、初期化をしなかったり、範囲指定を間違えたりすると、想定と違う動作になることもあります。

本記事ではC言語のrandの使い方を、基礎から範囲指定・初期化・実践例まで体系的に解説していきます。

C言語のrand関数とは

rand関数の役割と概要

rand関数は、C言語標準ライブラリに含まれる疑似乱数生成関数です。

完全なランダムではありませんが、一般的なゲームや簡易シミュレーションなどで使うには十分な乱数を生成できます。

rand関数の基本仕様

rand関数の定義は、C言語の標準ヘッダstdlib.hに含まれています。

戻り値の範囲や型を、まずは表で整理します。

項目内容
ヘッダファイル#include <stdlib.h>
関数プロトタイプint rand(void);
戻り値の型int
戻り値の範囲0 以上 RAND_MAX 以下
マクロRAND_MAX実装依存(多くの環境で 32767 など)

重要なポイントは次の2つです。

1つ目は乱数の上限はRAND_MAXで決まること、2つ目は必ずstdlib.hをインクルードする必要があることです。

図解で理解するrandのイメージ

このようなイメージを持っておくと、後で解説する「範囲指定」の話が理解しやすくなります。

randを使うための準備

必要なヘッダファイル

rand関数を利用する前に、必ずstdlib.hをインクルードします。

インクルードしないと、コンパイラによっては警告やエラーが出る、あるいは未定義動作になる可能性があるため注意が必要です。

最小構成のサンプルコード

C言語
#include <stdio.h>   // printfを使うために必要
#include <stdlib.h>  // rand, RAND_MAXを使うために必要

int main(void) {
    // rand関数を1回呼び出して結果を表示する簡単な例
    int value = rand();   // 0 ~ RAND_MAXの範囲の整数が返る

    printf("rand() = %d\n", value);
    printf("RAND_MAX = %d\n", RAND_MAX);

    return 0;
}
実行結果
rand() = 1804289383
RAND_MAX = 2147483647

この出力例ではRAND_MAXが 2147483647 になっていますが、これは環境依存です。

自分の環境でプログラムを実行して確認しておくと安心です。

randの基本的な使い方

そのまま使う場合

最も単純な使い方は、rand()を直接呼び出して、その値を利用する方法です。

これは「0 以上 RAND_MAX 以下の一様な整数乱数」をそのまま使うケースです。

複数回呼び出してみる例

C言語
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    // rand()を複数回呼んで、どのような値が出てくるか確認する
    for (int i = 0; i < 5; i++) {
        int r = rand();    // 毎回違う整数が返ってくるとは限らない(後で説明)
        printf("rand() call %d: %d\n", i + 1, r);
    }

    printf("RAND_MAX = %d\n", RAND_MAX);

    return 0;
}
実行結果
rand() call 1: 1804289383
rand() call 2: 846930886
rand() call 3: 1681692777
rand() call 4: 1714636915
rand() call 5: 1957747793
RAND_MAX = 2147483647

注意したいのは、「プログラムを毎回実行したときに、同じ列の乱数が出てくる場合がある」という点です。

これは初期化(シード設定)をしていないためで、次のセクションで詳しく解説します。

randとsrandによる初期化(シード設定)

なぜ初期化が必要なのか

rand関数は「疑似」乱数を生成します。

内部的にはある初期値(シード値)から一定の計算手順に従って値を生成しているため、同じシード値からは同じ乱数列が必ず得られます

初期化を行わない場合、多くの処理系では固定のシード値が使われるため、プログラムを起動するたびに同じ乱数列が生成されてしまいます。

ゲームなどで毎回同じパターンになってしまうのは不自然なので、通常は毎回異なるシード値を設定してからrand()を使うのが一般的です。

srand関数によるシード設定

シード値の設定にはsrand関数を使います。

項目内容
ヘッダファイル#include <stdlib.h>
関数プロトタイプvoid srand(unsigned int seed);
引数シード値(任意のunsigned int)
効果以後のrand()の乱数列が変わる

シード値を変えれば乱数列も変わるため、現在時刻など、毎回変化する値をシードに使うのが典型的です。

図解で理解するrandとsrand

この図のように、シード値から乱数列が一意に決まるイメージを持っておくと、デバッグや再現性のある実験にも役立ちます。

time関数を使って毎回異なる乱数列にする

実際によく使われるパターンは、time関数の戻り値をシードにする方法です。

timetime_t time(time_t *tp);という関数で、1970年1月1日からの経過秒数などを返します。

毎回プログラムを実行するタイミングが違えば、この値も変わるため、シード値として利用しやすいのです。

C言語
#include <stdio.h>
#include <stdlib.h>  // rand, srand
#include <time.h>    // time

int main(void) {
    // 現在時刻(秒)をシードとして設定
    // time(NULL) は time(0) と同じ意味
    srand((unsigned int)time(NULL));

    // シード設定後のrand()呼び出しは、毎回違う列の乱数になる可能性が高い
    for (int i = 0; i < 5; i++) {
        int r = rand();
        printf("rand() call %d: %d\n", i + 1, r);
    }

    return 0;
}

出力例(実行ごとに異なる可能性が高い):

実行結果
rand() call 1: 1576048367
rand() call 2: 1140279430
rand() call 3: 2027909356
rand() call 4: 127302616
rand() call 5: 1153293252

時間に基づくシード設定は、実運用ではほぼ必須といって良いほど一般的なテクニックです。

randで範囲を指定する方法の基本

なぜ範囲指定が必要なのか

多くの場面では「0以上RAND_MAX以下の整数」そのものではなく、「0〜9」や「1〜6」のように、用途に合った狭い範囲の乱数が欲しくなります。

例えば、サイコロの目であれば 1〜6 の整数が必要です。

rand関数は常に0〜RAND_MAXの整数を返すため、自分で計算して目的の範囲に変換する必要があります。

基本の考え方

乱数の範囲を[min, max]としたい場合、もっともよく使われる式は次のものです。

min + rand() % (max - min + 1)

ここで%剰余演算子で、ある数を割った余りを計算します。

この式は、rand() % (max - min + 1)0 〜 (max - min)の整数を作り、それにminを足すことでmin 〜 maxの範囲に移動させる仕組みになっています。

図解で理解する範囲指定

この図からも分かるように、割り算の余りで範囲を縮め、それを平行移動させるというのが基本戦略です。

整数乱数の範囲指定テクニック

0以上n未満の乱数を求める

最も基本的なパターンは「0以上n未満」の乱数です。

この場合はrand() % nで求めるのが定石です。

C言語
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
    srand((unsigned int)time(NULL));   // シードを現在時刻で初期化

    int n = 10;   // 0 ~ 9の10通りが欲しい

    for (int i = 0; i < 10; i++) {
        int r = rand() % n;  // 0 ~ 9のどれかが返る
        printf("r = %d\n", r);
    }

    return 0;
}

出力例(0〜9の値がランダムに出る):

実行結果
r = 3
r = 7
r = 0
r = 9
r = 2
r = 1
r = 4
r = 8
r = 5
r = 6

0以上n未満の乱数は、配列の添字などにもよく使われるため、確実に使い方を覚えておきたいところです。

min〜max(両端を含む)の乱数を求める

任意の範囲[min, max]の整数を取得したい場合、次の式を使います。

int r = min + rand() % (max - min + 1);

例えば、1〜6のサイコロをシミュレートする場合は、min = 1max = 6とすると良いです。

C言語
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
    srand((unsigned int)time(NULL));

    int min = 1;
    int max = 6;

    for (int i = 0; i < 10; i++) {
        int dice = min + rand() % (max - min + 1);  // 1 ~ 6
        printf("dice = %d\n", dice);
    }

    return 0;
}
実行結果
dice = 2
dice = 6
dice = 4
dice = 1
dice = 5
dice = 3
dice = 6
dice = 2
dice = 1
dice = 4

このように、「幅」= (max - min + 1) を求めてからrand() % 幅を実行し、最後にminを足すという流れで範囲指定を実現します。

randで負の数を含む範囲を扱う方法

負の値を含む範囲の考え方

たとえば-5〜5のように、負の値を含む乱数を生成したい場合でも、基本的な考え方は同じです。

minが負でも、max - min + 1で範囲の幅を計算できるからです。

int r = min + rand() % (max - min + 1);

この式はminが負でもそのまま利用できます。

-5〜5の乱数を生成する例

C言語
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
    srand((unsigned int)time(NULL));

    int min = -5;
    int max = 5;

    for (int i = 0; i < 10; i++) {
        int r = min + rand() % (max - min + 1);  // -5 ~ 5
        printf("r = %d\n", r);
    }

    return 0;
}
実行結果
r = -1
r = 5
r = -3
r = 0
r = 2
r = -5
r = 4
r = 1
r = -2
r = 3

必ず(max - min + 1)が正の値になるように範囲を設定してください。

min > maxのように逆転していると、意図しない挙動になります。

実数(浮動小数点)の乱数を求める方法

0.0〜1.0未満の実数乱数

多くのアルゴリズムでは実数の乱数が必要になります。

rand関数は整数を返しますが、浮動小数点型に変換して割り算を行うことで、0.0〜1.0未満の実数乱数を得ることができます。

一般的な式は次の通りです。

(double)rand() / (RAND_MAX + 1.0)

ここでキャスト除算によって、0.0以上1.0未満の値が得られます。

C言語
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
    srand((unsigned int)time(NULL));

    for (int i = 0; i < 5; i++) {
        // 0.0以上1.0未満の乱数を生成
        double r = (double)rand() / (RAND_MAX + 1.0);
        printf("r = %f\n", r);
    }

    return 0;
}
実行結果
r = 0.573829
r = 0.102345
r = 0.987612
r = 0.345671
r = 0.700123

「1.0以下」ではなく「1.0未満」である点に注意してください。

任意の実数範囲[a, b)の乱数

0.0〜1.0未満の乱数uが得られれば、任意の実数範囲[a, b)の乱数は次の式で生成できます。

a + (b - a) * u

つまり、u(b - a)倍してからaを足すことで、a以上b未満の実数に変換できます。

例: 0.0〜10.0未満の乱数

C言語
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
    srand((unsigned int)time(NULL));

    double a = 0.0;
    double b = 10.0;

    for (int i = 0; i < 5; i++) {
        double u = (double)rand() / (RAND_MAX + 1.0);  // 0.0 ~ 1.0未満
        double x = a + (b - a) * u;                    // 0.0 ~ 10.0未満
        printf("x = %f\n", x);
    }

    return 0;
}
実行結果
x = 1.234567
x = 9.876543
x = 5.678901
x = 0.123456
x = 7.654321

例: -1.0〜1.0未満の乱数

C言語
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
    srand((unsigned int)time(NULL));

    double a = -1.0;
    double b = 1.0;

    for (int i = 0; i < 5; i++) {
        double u = (double)rand() / (RAND_MAX + 1.0);  // 0.0 ~ 1.0未満
        double x = a + (b - a) * u;                    // -1.0 ~ 1.0未満
        printf("x = %f\n", x);
    }

    return 0;
}
実行結果
x = -0.345678
x = 0.912345
x = -0.123456
x = 0.567890
x = -0.789012

整数乱数と実数乱数の双方で、「基準(0〜1)を作ってから拡大・平行移動する」という考え方は共通です。

randの注意点と落とし穴

乱数の「質」と用途の限界

rand関数は簡単に使える一方で、乱数の質がそれほど高くないという欠点もあります。

多くの実装では線形合同法というシンプルなアルゴリズムを使っており、暗号用途などの高い安全性が要求される場面には不向きです。

一般的には次のような用途には問題なく使用できます。

  • 授業や学習用途のサンプルコード
  • 簡易なゲームロジック
  • 軽いシミュレーションやテストデータ生成

一方で、次のような用途ではrandではなく、より高品質な乱数生成器や暗号用ライブラリを検討すべきです。

  • 暗号鍵の生成
  • セキュリティトークン、ワンタイムパスワード
  • 公平性が厳しく問われる抽選システム

剰余演算(%)による偏りの問題

rand() % nという書き方は簡単ですが、乱数の分布にわずかな偏りが入る可能性があります。

これはRAND_MAX + 1nの倍数でない場合に、余りの出現確率が完全には均等にならないためです。

通常のプログラムではこの偏りはほとんど問題にならないことが多いですが、厳密な均一性が求められる場合には注意が必要です。

より均一な整数乱数を得るテクニック(応用)

どうしてもrand() % nの偏りを避けたい場合は、「受け入れ・棄却法」と呼ばれる手法を用いることがあります。

これは、偏りが出る可能性がある範囲の乱数を捨てることで、残りの値だけから均一な分布を得るというテクニックです。

C言語
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int uniform_rand_int(int n) {
    // n未満の乱数を偏りを抑えて生成する例
    int r;
    int limit = RAND_MAX - (RAND_MAX % n); // このlimit未満の値だけ受け入れる

    do {
        r = rand();
    } while (r >= limit);  // 偏りが出る可能性のある上側の値を捨てる

    return r % n;  // 均一な 0 ~ n-1が得られる
}

int main(void) {
    srand((unsigned int)time(NULL));

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

    return 0;
}
実行結果
3 7 1 0 9 4 6 2 8 5 1 4 7 3 0 2 9 6 8 5

この方法はループで何度か乱数を引き直す可能性があるため、処理時間とのトレードオフがあります。

とはいえ、n がそこまで大きくない場合やRAND_MAXが十分大きい環境では、実用上問題にならないことが多いです。

randを使った実用的なサンプル集

サンプル1: サイコロを振るプログラム

まずは代表的な例としてサイコロを振るプログラムを作ってみます。

C言語
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
    // 現在時刻でシードを初期化
    srand((unsigned int)time(NULL));

    // 6面サイコロを10回振って結果を表示する
    for (int i = 0; i < 10; i++) {
        int dice = 1 + rand() % 6;  // 1 ~ 6の乱数
        printf("%d回目のサイコロ: %d\n", i + 1, dice);
    }

    return 0;
}
実行結果
1回目のサイコロ: 3
2回目のサイコロ: 6
3回目のサイコロ: 1
4回目のサイコロ: 5
5回目のサイコロ: 2
6回目のサイコロ: 4
7回目のサイコロ: 6
8回目のサイコロ: 3
9回目のサイコロ: 1
10回目のサイコロ: 5

ゲーム開発やシミュレーションなどでよく使う形なので、このパターンはぜひ覚えておくとよいです。

サンプル2: 配列をランダムにシャッフルする(Fisher-Yates法)

配列の要素をランダムに並べ替えることをシャッフルと呼びます。

トランプの並び替えや、ランダムな順番での出題などで頻繁に利用されます。

Fisher-Yatesシャッフルは、正しい一様性を持つ代表的なシャッフルアルゴリズムです。

C言語
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

// 配列をFisher-Yates法でシャッフルする関数
void shuffle(int *array, int n) {
    for (int i = n - 1; i > 0; i--) {
        // 0 ~ iの範囲からランダムにインデックスを選ぶ
        int j = rand() % (i + 1);

        // array[i] と array[j] を交換する
        int temp   = array[i];
        array[i]   = array[j];
        array[j]   = temp;
    }
}

int main(void) {
    srand((unsigned int)time(NULL));

    int a[] = {1, 2, 3, 4, 5};
    int n = (int)(sizeof(a) / sizeof(a[0]));

    printf("シャッフル前: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", a[i]);
    }
    printf("\n");

    shuffle(a, n);

    printf("シャッフル後: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", a[i]);
    }
    printf("\n");

    return 0;
}
実行結果
シャッフル前: 1 2 3 4 5 
シャッフル後: 3 5 1 4 2

実行するたびに、配列の並び順がランダムに入れ替わることが確認できます。

サンプル3: ランダムな文字列(英数字)を生成する

簡易的なIDやパスワードのようなランダムな英数字文字列を生成する例を示します。

実際のパスワード用途にはrandは推奨されませんが、テストデータやサンプルとしては便利です。

C言語
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

// ランダムな英数字(0-9, A-Z, a-z)からなる文字を1つ返す関数
char random_alnum(void) {
    const char charset[] =
        "0123456789"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "abcdefghijklmnopqrstuvwxyz";
    int n = (int)(sizeof(charset) - 1);   // 終端の'\0'を除く
    int index = rand() % n;              // 0 ~ n-1
    return charset[index];
}

// 長さlenのランダムな英数字文字列を生成し、bufに書き込む関数
void random_string(char *buf, int len) {
    for (int i = 0; i < len; i++) {
        buf[i] = random_alnum();
    }
    buf[len] = '\0';  // 文字列終端
}

int main(void) {
    srand((unsigned int)time(NULL));

    char buf[17];   // 16文字 + 終端
    random_string(buf, 16);

    printf("ランダム文字列: %s\n", buf);

    return 0;
}
実行結果
ランダム文字列: a9F3kL0Zx7B2qW1c

固定長の英数字IDやテスト用トークンを作る場面で応用しやすいパターンです。

randの代替とC11以降の乱数

C標準ライブラリの限界と他の選択肢

現在のC標準(C11, C17など)でも、標準ライブラリにはrand以外の乱数生成関数は基本的に存在しません

より高品質な乱数が必要な場合は、次のような選択肢を検討します。

  • OS依存のAPI(例: Unix系の/dev/urandomやWindowsの暗号API)
  • 外部ライブラリ(例: OpenSSL, libsodium など)
  • 自前で高品質な乱数生成器(例: Mersenne Twister)を実装・利用

暗号用途セキュリティが重要なシステムでは、randsrandのみで実装しないように注意してください。

C++との比較(参考)

C++11以降では<random>ヘッダに高品質な乱数生成エンジンや分布クラスが標準化されています。

C言語しか使えない環境ではこれらを直接使うことはできませんが、「よりよい乱数APIのイメージ」を知る意味では参考になります。

まとめ

C言語のrand関数は、疑似乱数を簡単に扱うための標準的な手段です。

本記事ではrandsrandの基本仕様から、シードの初期化方法、整数や実数への範囲指定、負の値を含む乱数の生成、シャッフルやランダム文字列といった実用例まで、段階的に説明しました。

重要なのは、シードを必ずsrandで初期化すること範囲指定の式(特にmin + rand() % (max - min + 1))を正しく使うこと、そしてセキュリティ用途にはrandを使わないという3点です。

これらを押さえておけば、日常的なプログラミングで乱数に困ることはほとんどなくなるはずです。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!