randは、引き続く呼び出しが擬似乱数列を返すような関数に付けられる名前である。ランドランダムと呼ばれている。以下、主に標準Cライブラリのそれについて説明する。

概要 編集

以下、基本的にC99に従う。

C言語ヘッダーファイル stdlib.h で宣言されている、0以上かつ定数RAND_MAX以下(数学で使う「以上」「以下」であり両端を含む)の整数値を返す関数である。標準ではマルチスレッドについて触れられておらず、POSIXではスレッドセーフに実装することを要求していない[1]

また、標準は、randが生成すべき乱数列の品質など、乱数列の乱数性については何も言及していない。当然移植性は保証されない。

初期化 編集

randの引き続く呼び出しが返す乱数列はsrandで初期化される。srandを呼び出さずにrandを使った場合は、最初に引数を1としてsrandを呼び出した場合と同じように動作しなければならない。srandに相当するステートメントが、一部のBASICでRANDOMIZEという名前であったためランダマイズと呼ばれることもあるが、「乱数シード英語版 (種子)」を与えているだけで、何かをランダムにしているわけではない。

実行ごとに異なる乱数列を生成するために、簡便な手法としては時刻などが使われる(後述のコード例を参照)。暗号などの応用では外部から予測が不可能(ないし十分に困難)な方法を使わなければならない。逆に、シミュレーションを再現するなどの用途では、同じ乱数シードを使用して同じ乱数列を返すようにする。

srandは乱数に種子を与え初期化するものであるから、randを使用する度にsrandを呼んだりするのは、誤った用法である。

randの問題点 編集

古いrandの実装が生成する乱数列は、問題があるものがほとんどだったことが指摘されている[2]現代[いつ?]のライブラリでは問題があるものは少なくなっている[要出典]が、標準の規格書で示された実装例があまり良いものではなかったことや、古いライブラリと同じコードが使われ続けているものもまだあることから、注意を要する。

前述の規格書に示された例をはじめ、randの実装に線形合同法が使われていることがあるので、線形合同法の欠点に注意する必要がある。詳細は線形合同法#短所を参照すること。

ライブラリによっては、標準外だがより高品質のrandom、rand48等が用意されていることがある。本格的な用途には、メルセンヌ・ツイスタ等のより良い生成法を検討すべきである。

srandとシードの問題点 編集

srand()にtime()等で得た現在時刻 (秒単位) を渡して初期化する方法はよく見かけるが、srandの実装によってはシード値が近いとrandによって生成される乱数も相関性の高い値が出力されるものがある。つまり下記例のような実装方法を採るプログラムを起動してから数秒後に同じプログラムを起動すると最初のうちは同じ乱数列を得る可能性が高い。これを回避するためにはtimeで得た値をハッシュ関数に通してからsrandに渡す、もしくはsrandを呼び出した後のrandは数十〜数百回読み飛ばすなどの対策が必要である。また言うまでもなく同じロジックを採用したプログラムが同一時刻に起動されるなどしてsrandが同一の値で初期化された場合は以降全く同じ乱数列を得ることになる。

形式 編集

#include <stdlib.h>
int rand(void);
void srand(unsigned);

コード例 編集

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

int main(void)
{
    int a;
    srand((unsigned int) time(0)); /* 現在時刻を取得して乱数シードを初期化する。 */
    a = (int)((rand() / ((double) RAND_MAX + 1.0)) * 10); /* [0, 9] の範囲の値のいずれかが返る。 */
    printf("%d", a);
    return 0;
}

出力結果の例:

8

特定の範囲で乱数を求めたいときにはa = rand() % 10とする方法も広く知られているが、線形合同法などの下位ビットの乱数としての品質が低い生成法に備えるため、上記のコード例のように上位にあるビットを利用することが推奨されている[3]。とはいえ両コードともrandの質とは関係なく分布に偏りが発生する方法であり注意が必要である。

参考文献 編集

外部リンク 編集