【PHP】mt_rand 関数から得られる値のランダム性の限界

  • 2022年4月18日
  • PHP

 PHP にはランダムな値を取得する関数がいくつかあります。その中でよくテストデータ作成で使うのは以下の2つです。mt_rand は数値を取得し、random_bytes はバイト列を取得しよく文字列に変換します。この mt_rand 関数のランダム性の限界について紹介します。この記事では PHP 8.1 を扱います。

PHP: mt_rand – Manual
PHP: random_bytes – Manual

 ざっくばらんに結論からいうと PHP のランダムな値は mt_getrandmax 関数で得られる値までの長さの範囲でしかランダム性を最大に取れません。例えばmt_rand(0, PHP_INT_MAX)で得られる値は期待した通りに散らばらない、とかそういう具合です。
PHP: mt_getrandmax – Manual

警告

min から maxまでの幅を mt_getrandmax() の範囲内におさめる必要があります。
つまり、(maxmin) <= mt_getrandmax()でなければいけないということです。この範囲をこえてしまうと、mt_rand() が返す値のランダム性が、本来あるべき姿よりも低くなってしまいます。

 この限界は PHP の mt_rand 関数がとる値の範囲が 32bit で表現できる値に限られているために生まれている限界です。mt_rand 関数のランダムな値を得る部分の実装部が次です。

php-src/mt_rand.c at PHP-8.1.4 · php/php-src#L157

php-src/mt_rand.c at PHP-8.1.4 · php/php-src#L312

PHPAPI uint32_t php_mt_rand(void)
{
	/* Pull a 32-bit integer from the generator state
	   Every other access function simply transforms the numbers extracted here */

	uint32_t s1;

	if (UNEXPECTED(!BG(mt_rand_is_seeded))) {
		zend_long bytes;
		if (php_random_bytes_silent(&bytes, sizeof(zend_long)) == FAILURE) {
			bytes = GENERATE_SEED();
		}
		php_mt_srand(bytes);
	}

	if (BG(left) == 0) {
		php_mt_reload();
	}
	--BG(left);

	s1 = *BG(next)++;
	s1 ^= (s1 >> 11);
	s1 ^= (s1 <<  7) & 0x9d2c5680U;
	s1 ^= (s1 << 15) & 0xefc60000U;
	return ( s1 ^ (s1 >> 18) );
}

 uint32_t の値が返っています。これは unsigned integer 32bit type の略で 32bit の範囲で表せる 0 以上の整数であることを示します。mt_rand 関数のランダムな値はこの型のとれる値の範囲しか状態を持てないため、それ以上の範囲を mt_rand 関数の引数に渡しても思ったほどランダムになりません。もっともテストデータはざっくばらんなランダムで十分なのでそういう使い道ならば問題にはなりません。もしセキュリティ等で可能な限り強力な乱数が必要な場合は暗号論的に安全が保証されている random_int 関数を使いましょう。

PHP: random_int – Manual

 ちなみに PHP 7.0 以前は恐ろしい偏り方をします。問題を誘発しかねないので気にする必要があります。
Online PHP editor | output for 5EGOi#v7.0.33

<?php

$loopCount = 1e6;
$min = 0;
$max = mt_getrandmax()*2;// 限界を超えた偶数
// 大量にランダムな値を作って奇数偶数の数を集計
$res = [
    'odd' => 0,
    'even'=> 0,
];
while ($loopCount--) {
    $v = rand($min, $max);
    $v%2 ? $res['odd']++ : $res['even']++;
}
// 集計結果を表示
var_dump($res);
/*
array(2) {
  ["odd"]=>
  int(1000000)
  ["even"]=>
  int(0)
}
 */
>株式会社シーポイントラボ

株式会社シーポイントラボ

TEL:053-543-9889
営業時間:9:00~18:00(月〜金)
住所:〒432-8003
   静岡県浜松市中央区和地山3-1-7
   浜松イノベーションキューブ 315
※ご来社の際はインターホンで「316」をお呼びください

CTR IMG