PHPの mt_rand() で生成される乱数はどのように初期化されているのか?

疑似乱数を使う時は、まず乱数に「種」(初期値)を与えて初期化しなければならないのですが、mt_srand()のマニュアルを読むと、

Note: As of PHP 4.2.0, there is no need to seed the random number generator with srand() or mt_srand() as this is now done automatically.

とあります。「乱数が初期化されていない場合、ランダムな値で初期化される」ということなので、大変便利に見えるのですが、どうも生成される乱数の分布が偏ってる気がする。ということで、初期化に使われる「ランダムな値」というのはどの程度ランダムなのか? 気になって調べてみました。

## 前提条件
Webなので、コネクション毎に乱数は初期化されます。1コネクションでmt_rand()を呼ぶ回数はあまり多くない(数回程度)なので、乱数の初期値でほぼ分布が決まります。

あと、調べたのは PHP 5.4.17 のソースです。

## mt_rand()のソースを調べる
まず、mt_rand() のソースは ext/standard/rand.c にあります。乱数が初期化されていない時は、以下の部分で初期化されます。

if (!BG(mt_rand_is_seeded)) {
php_mt_srand(GENERATE_SEED() TSRMLS_CC);
}

GENERATE_SEED() が乱数の種に使われているので、これを見てみましょう。ext/standard/php_rand.h に以下のような定義があります。

#define GENERATE_SEED() (((long) (time(0) * getpid())) ^ ((long) (1000000.0 * php_combined_lcg(TSRMLS_C))))

time(0) * getpid() と (1000000.0 * php_combined_lcg(TSRMLS_C)) の XOR を取っているわけですが、前者は現在時刻とプロセスIDなので、十分ランダムと言えるか疑問(分布の偏りもありそうだし、予測も難しくなさそう)。

次に php_combined_lcg() の方を見てみます。こちらは ext/standard/lcg.cにあります。

if (!LCG(seeded)) {
lcg_seed(TSRMLS_C);
}

で lcg_seed() を呼んでいるのですが、lcg_seed() の方はこんな感じ:

static void lcg_seed(TSRMLS_D) /* {{{ */
{
struct timeval tv;

if (gettimeofday(&tv, NULL) == 0) {
LCG(s1) = tv.tv_sec ^ (tv.tv_usec<<11); } else { LCG(s1) = 1; } #ifdef ZTS LCG(s2) = (long) tsrm_thread_id(); #else LCG(s2) = (long) getpid(); #endif /* Add entropy to s2 by calling gettimeofday() again */ if (gettimeofday(&tv, NULL) == 0) { LCG(s2) ^= (tv.tv_usec<<11); } LCG(seeded) = 1; } 結局これも現在時刻とプロセスIDでした。 ## /dev/urandomを使うには もちろん、この程度で十分な場合もあると思いますが、あまり分布に偏りがあっては困る場合などは /dev/urandom を乱数の種に使いたいところです。 PHP 5.3以降では openssl_random_pseudo_bytes() が使えるので、以下のように乱数を初期化するのが良いでしょう。 mt_srand(hexdec(bin2hex(openssl_random_pseudo_bytes(4)))); /dev/urandomを使った時の問題点として、エントロピーが枯渇した時に良い乱数が得られないというのがあります。これが問題になるような場合は、以下のようにチェックすると良いでしょう。 $strong = 0; mt_srand(hexdec(bin2hex(openssl_random_pseudo_bytes(4, $strong)))); // $strong が 1であることをチェック。0 になっていると、良い乱数が返って来ていない。

スポンサーリンク
スポンサーリンク:

フォローする