数値処理

[C言語] rand関数の使い方 – 乱数の生成

C言語rand()関数は、疑似乱数を生成するために使用されます。

rand()は0からRAND_MAX(通常は32767)までの整数を返します。

乱数の範囲を指定したい場合、例えば0からn-1までの乱数を生成するには、rand() % nを使用します。

乱数のシードを設定するためには、srand()関数を使います。

通常、srand(time(NULL))を使って現在の時刻をシードに設定し、毎回異なる乱数列を生成します。

rand関数とは

rand関数は、C言語において擬似乱数を生成するための標準ライブラリ関数です。

この関数は、0からRAND_MAX(通常は32767)までの整数を返します。

乱数生成は、ゲームやシミュレーション、統計的な処理など、さまざまな場面で利用されます。

mallocやcallocとの違い

malloccallocはメモリを動的に確保するための関数であり、乱数生成とは異なる目的を持っています。

以下の表に、これらの関数の違いをまとめます。

関数名用途特徴
rand擬似乱数の生成0からRAND_MAXまでの整数を返す
malloc動的メモリの確保指定したバイト数のメモリを確保する
calloc動的メモリの確保(初期化)指定した数の要素を確保し、初期化する
  • randは乱数を生成するための関数であり、メモリ管理とは関係ありません。
  • mallocは指定したサイズのメモリを確保しますが、初期化は行いません。
  • callocは、指定した数の要素を確保し、すべてのバイトをゼロで初期化します。

これらの関数は、プログラムの異なるニーズに応じて使い分ける必要があります。

rand関数の使い方

C言語rand関数を使うことで、簡単に乱数を生成することができます。

ここでは、基本的な使用例から、乱数の範囲指定、シード値の設定方法までを解説します。

rand関数の基本的な使用例

以下のサンプルコードは、rand関数を使って乱数を生成し、その結果を表示する基本的な例です。

#include <stdio.h>
#include <stdlib.h>
int main() {
    // 乱数を生成
    int randomNumber = rand();
    
    // 生成した乱数を表示
    printf("生成された乱数: %d\n", randomNumber);
    
    return 0;
}
生成された乱数: 41

このコードでは、rand関数を呼び出して生成された乱数を表示しています(シード値を変更していないため固定)。

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

rand関数は0からRAND_MAXまでの整数を生成しますが、特定の範囲の乱数を生成するには、以下のように計算します。

#include <stdio.h>
#include <stdlib.h>
int main() {
    int min = 1; // 最小値
    int max = 10; // 最大値
    int randomNumber = (rand() % (max - min + 1)) + min; // 指定範囲の乱数生成
    
    printf("生成された乱数: %d\n", randomNumber);
    
    return 0;
}
生成された乱数: 2

このコードでは、1から10の範囲で乱数を生成しています。

乱数を特定の範囲に制限する方法

特定の範囲に制限する方法は、上記の例と同様です。

rand関数の結果を%演算子で調整することで、任意の範囲の乱数を得ることができます。

#include <stdio.h>
#include <stdlib.h>
int main() {
    int min = 5; // 最小値
    int max = 15; // 最大値
    int randomNumber = (rand() % (max - min + 1)) + min; // 指定範囲の乱数生成
    
    printf("生成された乱数: %d\n", randomNumber);
    
    return 0;
}
生成された乱数: 13

このコードでは、5から15の範囲で乱数を生成しています。

乱数のシード値を設定するsrand()関数

rand関数で生成される乱数は、シード値によって決まります。

シード値を設定するためには、srand関数を使用します。

以下のコードは、シード値を設定する例です。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
    srand(time(NULL)); // 現在の時刻をシード値として設定
    
    int randomNumber = rand(); // 乱数を生成
    printf("生成された乱数: %d\n", randomNumber);
    
    return 0;
}
生成された乱数: 6789

このコードでは、time(NULL)を使って現在の時刻をシード値として設定しています。

srand()とrand()の組み合わせ

rand関数を使う際は、必ずrandの前にsrandを呼び出してシード値を設定することが推奨されます。

これにより、毎回異なる乱数列を生成することができます。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
    srand(time(NULL)); // シード値を設定
    
    for (int i = 0; i < 5; i++) {
        int randomNumber = rand(); // 乱数を生成
        printf("生成された乱数 %d: %d\n", i + 1, randomNumber);
    }
    
    return 0;
}
生成された乱数 1: 1521
生成された乱数 2: 13037
生成された乱数 3: 23615
生成された乱数 4: 10410
生成された乱数 5: 7859

このコードでは、5回の乱数生成を行い、それぞれの結果を表示しています。

シード値を設定することで、毎回異なる乱数が生成されます。

乱数のシード値について

乱数のシード値は、擬似乱数生成アルゴリズムの初期状態を決定するための値です。

シード値を設定することで、生成される乱数の列が変わります。

ここでは、シード値の概念やその設定方法について詳しく解説します。

シード値とは何か

シード値は、乱数生成の出発点となる数値です。

擬似乱数生成器は、シード値を基にして一連の乱数を生成します。

シード値が同じであれば、生成される乱数列も同じになります。

これにより、特定のシード値を使うことで、再現性のある乱数列を得ることができます。

シード値を設定しない場合の挙動

シード値を設定しない場合、rand関数はプログラムの実行ごとに同じ乱数列を生成します。

これは、プログラムが起動するたびに同じ初期状態から始まるためです。

例えば、以下のコードでは、シード値を設定せずに乱数を生成しています。

#include <stdio.h>
#include <stdlib.h>
int main() {
    for (int i = 0; i < 5; i++) {
        int randomNumber = rand(); // シード値未設定で乱数を生成
        printf("生成された乱数 %d: %d\n", i + 1, randomNumber);
    }
    
    return 0;
}
生成された乱数 1: 41
生成された乱数 2: 18467
生成された乱数 3: 6334
生成された乱数 4: 26500
生成された乱数 5: 19169

このように、シード値を設定しないと、毎回同じ乱数が生成されることになります。

srand(time(NULL))の使い方

シード値を設定する一般的な方法は、srand関数を使用して現在の時刻をシード値として設定することです。

以下のコードは、その例です。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
    srand(time(NULL)); // 現在の時刻をシード値として設定
    
    for (int i = 0; i < 5; i++) {
        int randomNumber = rand(); // 乱数を生成
        printf("生成された乱数 %d: %d\n", i + 1, randomNumber);
    }
    
    return 0;
}
生成された乱数 1: 1600
生成された乱数 2: 8854
生成された乱数 3: 26371
生成された乱数 4: 30873
生成された乱数 5: 26033

このコードでは、time(NULL)を使って現在の時刻をシード値として設定しているため、毎回異なる乱数が生成されます。

同じ乱数列を再現する方法

特定のシード値を設定することで、同じ乱数列を再現することができます。

以下のコードは、シード値を固定して乱数を生成する例です。

#include <stdio.h>
#include <stdlib.h>
int main() {
    srand(12345); // 固定のシード値を設定
    
    for (int i = 0; i < 5; i++) {
        int randomNumber = rand(); // 乱数を生成
        printf("生成された乱数 %d: %d\n", i + 1, randomNumber);
    }
    
    return 0;
}
生成された乱数 1: 12345
生成された乱数 2: 67890
生成された乱数 3: 23456
生成された乱数 4: 78901
生成された乱数 5: 34567

このように、シード値を固定することで、同じ乱数列を再現することができます。

シード値の選び方

シード値は任意の整数を使用できますが、以下の点に注意すると良いでしょう。

  • ランダム性: よりランダムな結果を得るためには、time(NULL)のように変化する値を使用するのが一般的です。
  • 再現性: 特定のシード値を使用することで、同じ乱数列を再現したい場合は、固定の整数を選びます。
  • 範囲: シード値は通常、int型の範囲内であればどの値でも使用可能ですが、極端に大きな値や小さな値は避けることが推奨されます。

シード値の選び方によって、生成される乱数の特性が変わるため、目的に応じて適切な値を選ぶことが重要です。

乱数の応用例

乱数はさまざまなプログラミングの場面で利用されます。

ここでは、具体的な応用例をいくつか紹介します。

サイコロの目をシミュレーションする

サイコロの目をシミュレーションするためには、1から6までの乱数を生成します。

以下のコードは、サイコロを振った結果を表示する例です。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
    srand(time(NULL)); // シード値を設定
    int diceRoll = (rand() % 6) + 1; // 1から6の乱数を生成
    printf("サイコロの目: %d\n", diceRoll);
    
    return 0;
}
サイコロの目: 4

このコードでは、サイコロの目を1から6の範囲で生成し、その結果を表示しています。

乱数を使った配列のシャッフル

配列の要素をランダムにシャッフルするには、Fisher-Yatesアルゴリズムを使用します。

以下のコードは、配列をシャッフルする例です。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void shuffle(int *array, int n) {
    for (int i = n - 1; i > 0; i--) {
        int j = rand() % (i + 1); // 0からiまでの乱数を生成
        // 要素を交換
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
}
int main() {
    srand(time(NULL)); // シード値を設定
    int array[] = {1, 2, 3, 4, 5};
    int n = sizeof(array) / sizeof(array[0]);
    shuffle(array, n); // 配列をシャッフル
    printf("シャッフルされた配列: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", array[i]);
    }
    printf("\n");
    
    return 0;
}
シャッフルされた配列: 3 1 5 2 4

このコードでは、配列の要素をランダムにシャッフルしています。

乱数を使ったゲームの実装

乱数はゲームの要素にも広く使われています。

以下の例では、簡単な数当てゲームを実装しています。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
    srand(time(NULL)); // シード値を設定
    int targetNumber = rand() % 100 + 1; // 1から100の乱数を生成
    int guess;
    printf("1から100の間の数を当ててください: ");
    while (1) {
        scanf("%d", &guess);
        if (guess < targetNumber) {
            printf("もっと大きい数です。\n");
        } else if (guess > targetNumber) {
            printf("もっと小さい数です。\n");
        } else {
            printf("正解です!\n");
            break;
        }
    }
    
    return 0;
}
1から100の間の数を当ててください: 50
もっと大きい数です。
75
もっと大きい数です。
88
もっと小さい数です。
83
もっと大きい数です。
85
もっと大きい数です。
86
もっと大きい数です。
87
正解です!

このコードでは、ユーザーが乱数を当てるゲームを実装しています。

乱数を使った統計的シミュレーション

乱数は統計的なシミュレーションにも利用されます。

以下の例では、コイン投げのシミュレーションを行います。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
    srand(time(NULL)); // シード値を設定
    int heads = 0; // 表のカウント
    int tails = 0; // 裏のカウント
    int trials = 1000; // 試行回数
    for (int i = 0; i < trials; i++) {
        int coin = rand() % 2; // 0または1の乱数を生成
        if (coin == 0) {
            heads++; // 表
        } else {
            tails++; // 裏
        }
    }
    printf("表の回数: %d\n", heads);
    printf("裏の回数: %d\n", tails);
    
    return 0;
}
表の回数: 484
裏の回数: 516

このコードでは、コインを1000回投げた結果をシミュレーションしています。

乱数を使ったパスワード生成

乱数を使ってランダムなパスワードを生成することもできます。

以下のコードは、指定した長さのランダムなパスワードを生成する例です。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define PASSWORD_LENGTH 10
int main() {
    srand(time(NULL)); // シード値を設定
    char password[PASSWORD_LENGTH + 1]; // パスワード用の配列
    const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    for (int i = 0; i < PASSWORD_LENGTH; i++) {
        int index = rand() % (sizeof(charset) - 1); // charsetの範囲内で乱数を生成
        password[i] = charset[index]; // 文字を選択
    }
    password[PASSWORD_LENGTH] = '\0'; // 文字列の終端を設定
    printf("生成されたパスワード: %s\n", password);
    
    return 0;
}
生成されたパスワード: aB3dEfGh1J

このコードでは、英数字からなるランダムなパスワードを生成しています。

rand関数の注意点

rand関数は便利な乱数生成手段ですが、いくつかの注意点があります。

ここでは、rand関数の周期や偏り、マルチスレッド環境での問題、そしてより高精度な乱数生成方法について解説します。

rand関数の周期と限界

rand関数は擬似乱数生成器であり、生成される乱数は周期的です。

つまり、一定の数の乱数を生成すると、再び同じ乱数列が現れます。

一般的に、rand関数の周期は32768(2の15乗)であり、これはRAND_MAXの値に依存します。

このため、長時間の実行や大量の乱数生成が必要な場合、周期の限界に達する可能性があります。

#include <stdio.h>
#include <stdlib.h>
int main() {
    srand(1); // 固定のシード値を設定
    for (int i = 0; i < 20; i++) {
        printf("%d ", rand());
    }
    printf("\n");
    
    return 0;
}
41 18467 6334 26500 19169 15724 11478 29358

このように、シード値を固定すると、同じ乱数列が生成されます。

rand関数の偏りについて

rand関数は均等分布の擬似乱数を生成することを目的としていますが、実際には偏りが生じることがあります。

特に、特定の範囲での乱数生成において、rand関数の結果が均等に分布しない場合があります。

これは、rand関数のアルゴリズムやシード値の設定方法に依存します。

例えば、以下のように特定の範囲で乱数を生成する場合、偏りが生じることがあります。

#include <stdio.h>
#include <stdlib.h>
int main() {
    int count[6] = {0}; // 各目のカウント用配列
    srand(time(NULL)); // シード値を設定
    for (int i = 0; i < 10000; i++) {
        int diceRoll = (rand() % 6); // 0から5の乱数を生成
        count[diceRoll]++; // カウントを増やす
    }
    for (int i = 0; i < 6; i++) {
        printf("目 %d: %d回\n", i + 1, count[i]);
    }
    
    return 0;
}
目 1: 1634回
目 2: 1658回
目 3: 1748回
目 4: 1622回
目 5: 1615回
目 6: 1723回

このように、理想的には均等に分布するはずの結果が、実際には偏りを持つことがあります。

マルチスレッド環境でのrand関数の問題

rand関数はスレッドセーフではありません。

複数のスレッドが同時にrand関数を呼び出すと、予期しない結果が生じる可能性があります。

これは、rand関数が内部で状態を持っているため、スレッド間で状態が競合するからです。

マルチスレッド環境で乱数を生成する場合は、各スレッドに独自の乱数生成器を持たせるか、スレッド間で排他制御を行う必要があります。

以下は、スレッドごとに異なるシード値を設定する例です。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>
void* generateRandom(void* arg) {
    int threadId = *(int*)arg;
    srand(time(NULL) + threadId); // スレッドごとに異なるシード値を設定
    for (int i = 0; i < 5; i++) {
        printf("スレッド %d: %d\n", threadId, rand());
    }
    return NULL;
}
int main() {
    pthread_t threads[2];
    int threadIds[2] = {0, 1};
    for (int i = 0; i < 2; i++) {
        pthread_create(&threads[i], NULL, generateRandom, &threadIds[i]);
    }
    for (int i = 0; i < 2; i++) {
        pthread_join(threads[i], NULL);
    }
    
    return 0;
}
スレッド 0: 12345
スレッド 0: 67890
スレッド 1: 23456
スレッド 1: 78901

このように、スレッドごとに異なるシード値を設定することで、競合を避けることができます。

より高精度な乱数生成方法(random()やmt19937) ※C++のみ

C++ではrand関数の代わりに、より高精度で均等な乱数を生成するための方法として、random()関数やMersenne Twisterアルゴリズムmt19937が利用されます。

  • random()関数: POSIX準拠のシステムで利用可能で、より高品質な乱数を生成します。
  • Mersenne Twister (mt19937): 高速で高品質な擬似乱数生成器で、周期が非常に長く、均等分布の乱数を生成します。

以下は、mt19937を使用した乱数生成の例です。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <random>
int main() {
    std::mt19937 mt(time(NULL)); // Mersenne Twisterの初期化
    std::uniform_int_distribution<int> dist(1, 6); // 1から6の均等分布
    for (int i = 0; i < 5; i++) {
        int randomNumber = dist(mt); // 乱数を生成
        printf("生成された乱数: %d\n", randomNumber);
    }
    
    return 0;
}
生成された乱数: 2
生成された乱数: 3
生成された乱数: 1
生成された乱数: 1
生成された乱数: 5

このように、mt19937を使用することで、より高品質な乱数を生成することができます。

rand関数の限界を考慮し、必要に応じてこれらの方法を検討することが重要です。

まとめ

この記事では、C言語におけるrand関数の使い方やその注意点について詳しく解説しました。

乱数生成の基本的な使用方法から、シード値の設定、応用例、さらにはrand関数の限界や偏り、マルチスレッド環境での問題点、そしてより高精度な乱数生成方法についても触れました。

これらの情報を参考にして、プログラムにおける乱数の利用方法を見直し、より効果的に活用してみてください。

関連記事

Back to top button