6日でできる 新C言語入門|rand関数とsrand関数の使い方

乱数を使えば、毎回違う結果を作れる。
rand関数とsrand関数を理解して、サイコロ、くじ引き、ゲーム処理に使えるランダムな値を扱おう。

C言語では、プログラムの中でランダムな数値を作るために、rand関数を使います。

乱数は、ゲーム、くじ引き、サイコロ、カードのシャッフル、簡単なシミュレーションなど、さまざまな場面で使われます。たとえば、サイコロの目を1から6の中から決めたい場合や、1から100までの中から当たり番号を選びたい場合に便利です。

ただし、rand関数だけを使うと、プログラムを実行するたびに同じ順番の数値が出ることがあります。これは、C言語のrand関数が完全に予測不能な数を作っているのではなく、一定の計算ルールに従って乱数らしい値を作っているためです。

そこで使うのがsrand関数です。

srand関数は、乱数の出発点となるシード値を設定するための関数です。シード値を変えることで、rand関数が作る乱数の並びも変わります。よく使われる方法は、現在時刻を返すtime関数と組み合わせて、実行するたびに違うシード値を設定する方法です。

この記事では、rand関数、srand関数、time関数の役割、1から6のような範囲指定の方法、指定した範囲の乱数を複数回表示する方法、RAND_MAXの意味、乱数を扱うときの注意点まで、サンプルプログラムを使ってやさしく解説していきます。

C言語で乱数を使うために必要な関数

C言語で乱数を扱うときは、主に次の関数を使います。

関数名役割使うヘッダファイル
rand関数0からRAND_MAXまでの整数乱数を返すstdlib.h
srand関数乱数のシード値を設定するstdlib.h
time関数現在時刻を取得するtime.h

rand関数は、乱数を1つ作る関数です。

srand関数は、rand関数が使う乱数の出発点を設定する関数です。

time関数は、現在時刻を取得する関数です。現在時刻は実行するタイミングによって変わるため、srand関数に渡すシード値としてよく使われます。

乱数を使うプログラムでは、次の3つのヘッダファイルを使うことが多いです。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

stdio.hはprintfなどの入出力に使います。
stdlib.hはrand関数とsrand関数に使います。
time.hはtime関数に使います。

rand関数だけでは毎回同じ並びになりやすい

rand関数は乱数を返しますが、何も初期化しないまま使うと、プログラムを実行するたびに同じ順番の値が出ることがあります。

これは、rand関数が擬似乱数を作る関数だからです。

擬似乱数とは、本当に自然に発生したランダムな値ではなく、計算によって乱数のように見える値を作る仕組みです。計算の出発点が同じなら、出てくる値の並びも同じになります。

この出発点がシード値です。

用語意味
乱数ランダムに見える数値
擬似乱数計算で作られる乱数のような数値
シード値乱数の並びを決める出発点
乱数系列rand関数が作る乱数の並び

毎回違う乱数を出したい場合は、srand関数でシード値を設定します。

srand関数で乱数の出発点を変える

srand関数は、rand関数が作る乱数の並びを変えるために使います。

よく使われる書き方は次の形です。

srand((unsigned)time(NULL));

time(NULL)は現在時刻をもとにした値を返します。実行する時刻が変われば値も変わるため、srand関数に渡すシード値として使いやすいです。

(unsigned)は、time関数の戻り値をsrand関数に渡しやすい型へ変換するために使っています。

乱数を使うときの基本的な流れは次のようになります。

手順内容
1srand関数で乱数のシード値を設定する
2rand関数で乱数を作る
3必要な範囲に変換する
4結果を表示または処理に使う

srand関数は、通常プログラムの最初で1回だけ呼び出します。rand関数を呼び出すたびにsrand関数を呼ぶ必要はありません。

図:rand関数とsrand関数の役割

この図から分かること

この図から分かるのは、rand関数だけで乱数を作るのではなく、srand関数で乱数の出発点を設定してからrand関数を使うという流れです。

time関数で現在時刻を取得し、その値をsrand関数に渡すことで、実行するたびに違うシード値を使えます。その結果、rand関数が返す乱数の並びも変わりやすくなります。

1から6までの乱数を作る考え方

rand関数は、そのまま使うと0からRAND_MAXまでの大きな範囲の値を返します。

サイコロのように1から6までの値が欲しい場合は、剰余演算子の%を使って範囲を調整します。

rand() % 6 + 1

この式は、次のように考えます。

得られる範囲
rand()0からRAND_MAX
rand() % 60から5
rand() % 6 + 11から6

rand() % 6だけでは0から5になります。そこで、最後に1を足すことで、1から6の範囲にしています。

この考え方は、サイコロ以外の範囲にも応用できます。

サイコロを3回振るプログラム

次のプログラムでは、サイコロを3回振り、それぞれの目と合計を表示します。

プロジェクト/ファイル名: Lesson46_1/main.c

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
    int dice1;
    int dice2;
    int dice3;
    int total;

    // 現在時刻を使って乱数のシード値を設定する
    srand((unsigned)time(NULL));

    // 1から6までの乱数を作る
    dice1 = rand() % 6 + 1;
    dice2 = rand() % 6 + 1;
    dice3 = rand() % 6 + 1;

    // 3回分の合計を求める
    total = dice1 + dice2 + dice3;

    printf("1回目のサイコロ: %d\n", dice1);
    printf("2回目のサイコロ: %d\n", dice2);
    printf("3回目のサイコロ: %d\n", dice3);
    printf("合計: %d\n", total);

    return 0;
}

実行例

1回目のサイコロ: 2
2回目のサイコロ: 6
3回目のサイコロ: 4
合計: 12

このプログラムでは、最初にsrand関数を呼び出して、乱数のシード値を設定しています。

srand((unsigned)time(NULL));

その後、rand関数を使って1から6までの乱数を作っています。

dice1 = rand() % 6 + 1;

rand関数で作った値を6で割った余りは0から5です。そこに1を足すことで、1から6の範囲にしています。

実行するタイミングによってシード値が変わるため、毎回違う結果になりやすくなります。

乱数の範囲を指定する方法

rand関数で作った乱数は、%演算子を使うことで範囲を調整できます。

代表的な書き方を整理すると、次のようになります。

作りたい範囲書き方
1からnrand() % n + 1rand() % 10 + 1
0からnrand() % (n + 1)rand() % 11
aからbrand() % (b - a + 1) + arand() % 6 + 3

たとえば、3から8までの乱数を作りたい場合は、次のように書きます。

rand() % 6 + 3

3から8には、3、4、5、6、7、8の6個の値があります。
まずrand() % 6で0から5を作り、そこに3を足すことで3から8になります。

このように、範囲aからbまでの乱数を作る基本形は次のようになります。

rand() % (b - a + 1) + a

範囲指定の考え方を分解する

範囲指定の式は、慣れるまでは少し分かりにくく感じるかもしれません。

たとえば、10から15までの乱数を作る場合を考えます。

10から15までの整数は、10、11、12、13、14、15の6個です。

そのため、まず0から5までの乱数を作ります。

rand() % 6

そして、最小値である10を足します。

rand() % 6 + 10

これで10から15までの値になります。

処理結果
rand() % 60から5
rand() % 6 + 1010から15

範囲指定では、値の個数を考えることが大切です。

b - a + 1は、aからbまでに含まれる整数の個数を表します。

図:範囲指定は余りと足し算で調整する

この図から分かること

この図から分かるのは、rand関数の戻り値をそのまま使うのではなく、%演算子と足し算で必要な範囲へ変換しているということです。

3から8の乱数が欲しい場合、まず0から5の範囲を作り、そこに3を足します。このように、作りたい範囲の個数と最小値を意識すると、乱数の範囲指定が分かりやすくなります。

10から20までの乱数を5回表示するプログラム

次のプログラムでは、10から20までの乱数を5回表示します。

プロジェクト/ファイル名: Lesson46_2/main.c

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
    int i;
    int number;

    // 現在時刻を使って乱数のシード値を設定する
    srand((unsigned)time(NULL));

    // 10から20までの乱数を5回表示する
    for (i = 0; i < 5; i++) {
        number = rand() % 11 + 10;
        printf("%d回目の乱数: %d\n", i + 1, number);
    }

    return 0;
}

実行例

1回目の乱数: 15
2回目の乱数: 20
3回目の乱数: 11
4回目の乱数: 18
5回目の乱数: 13

このプログラムでは、10から20までの乱数を作るために、次の式を使っています。

rand() % 11 + 10

10から20までの整数は11個あります。

範囲個数
10から2011個

そのため、rand() % 11で0から10の値を作り、そこに10を足します。

範囲
rand() % 110から10
rand() % 11 + 1010から20

for文を使って5回繰り返すことで、5個の乱数を表示しています。

srand関数を忘れた場合の動き

srand関数を使わずにrand関数だけを使うと、プログラムを実行するたびに同じ順番の乱数が表示されることがあります。

これは、rand関数の乱数系列が同じシード値から始まるためです。

たとえば、毎回次のような並びになることがあります。

1回目の乱数: 13
2回目の乱数: 17
3回目の乱数: 10
4回目の乱数: 19
5回目の乱数: 12

もう一度実行しても同じ順番になる場合、srand関数による初期化が行われていない可能性があります。

毎回違う結果が欲しい場合は、プログラムの最初にsrand関数を1回だけ呼び出します。

srand((unsigned)time(NULL));

ただし、同じ乱数の並びが必要な場面もあります。たとえば、テストやデバッグでは、毎回同じ結果になる方が確認しやすいことがあります。

目的srand関数の使い方
毎回違う結果が欲しい現在時刻をシード値にする
毎回同じ結果でテストしたい固定のシード値を使う
乱数の動きを確認したいシード値を変えて比較する

乱数は、毎回違えばよいというだけではありません。目的に応じて、シード値を変えるか固定するかを選ぶことが大切です。

RAND_MAXとは

rand関数が返す値の最大値はRAND_MAXという定数で表されます。

RAND_MAXは環境によって値が異なることがあります。ある環境では32767の場合もあれば、もっと大きな値の場合もあります。

RAND_MAXを確認したい場合は、次のようなプログラムで表示できます。

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    printf("RAND_MAX = %d\n", RAND_MAX);

    return 0;
}

rand関数は0からRAND_MAXまでの整数を返します。

関数戻り値の範囲
rand関数0からRAND_MAX

サイコロやくじ引きのように範囲が決まっている場合は、RAND_MAXまでの値をそのまま使うのではなく、%演算子で必要な範囲へ変換します。

乱数は完全なランダムではない

C言語のrand関数で作られる乱数は、擬似乱数です。

擬似乱数は、計算によって作られる乱数のような値です。そのため、シード値が同じなら、同じ順番の値が作られます。

これは欠点にも見えますが、テストでは便利な性質でもあります。シード値を固定すれば、同じ動作を何度も再現できるため、プログラムの確認がしやすくなります。

一方で、暗号やセキュリティ用途のように、予測されると困る乱数にはrand関数は向いていません。そのような用途では、より安全な乱数生成の仕組みを使う必要があります。

乱数を使うときの注意点

乱数を使うときは、次の点に注意しましょう。

注意点内容
srand関数は通常1回だけ呼び出す何度も呼ぶと乱数の並びが偏る原因になることがある
rand関数は擬似乱数を返す完全なランダムではない
範囲指定では個数を意識するaからbならb - a + 1個
%演算子による範囲指定には偏りが出る場合がある厳密なランダム性が必要な用途では注意する
暗号用途には使わないセキュリティ用途では専用の乱数生成方法が必要

学習段階では、rand関数、srand関数、time関数の組み合わせを理解し、サイコロやくじ引きのような簡単なプログラムで試すのがおすすめです。

図:srand関数は最初に1回だけ呼び出す

この図から分かること

この図から分かるのは、srand関数は乱数を作るたびに呼び出すものではなく、乱数の出発点を設定するために最初に1回呼び出すのが基本だということです。

srand関数でシード値を設定したあと、rand関数を何度も呼び出すことで、乱数の並びを順番に取り出せます。毎回違う結果にしたい場合は、time関数で現在時刻を使ってシード値を設定する方法がよく使われます。

rand関数とsrand関数を組み合わせるとランダムな処理を作れる

C言語で乱数を使う基本は、rand関数、srand関数、time関数を組み合わせることです。

rand関数は乱数を作ります。srand関数は乱数のシード値を設定します。time関数は現在時刻を取得し、実行するたびに変わりやすいシード値として使えます。

1から6までのサイコロの目を作る場合は、rand() % 6 + 1のように、%演算子と足し算を使って範囲を調整します。

10から20までの乱数なら、rand() % 11 + 10のように、範囲に含まれる個数と最小値を考えて式を作ります。

乱数を使えるようになると、サイコロ、くじ引き、ランダムな問題出題、ゲームの敵の行動、簡単なシミュレーションなどに応用できます。

ただし、rand関数で作る値は擬似乱数です。学習用や簡単なゲームには十分使えますが、暗号やセキュリティ用途には向いていません。

まずは、srand関数を最初に1回呼び出し、rand関数で乱数を作り、%演算子で必要な範囲へ変換するという基本の流れをしっかり身につけていきましょう。