PHPのmicrotimeの精度によるmt_srandへの影響

PHP
Screen Shot 2013-03-27 at 19.44.03

PHP5.3以下を使っていて、microtime()を元に乱数を生成している人は注意!

オフィシャルサイトのサンプルコード通りだとシードがランダムになりません。

PHPのユニットテストで確率を計算しているときに、「なかなか数字が分散しない」と思って調べました。

<?php
// seed with microseconds
function make_seed()
{
    list($usec, $sec) = explode(' ', microtime());
    return (float) $sec + ((float) $usec * 100000);
}

while (true) {
    var_dump(make_seed());
}

PHP5.2.14

これはやばい。

int(54)
int(54)
int(54)
int(54)
int(54)
int(54)
int(54)   
int(54)
int(54)
int(54)
int(54)
int(54)
int(54)
int(54)
int(54)
int(54)
int(54)
int(54)
int(54)
int(54)
int(54)
int(54) 
int(54)
int(54)
int(54)
int(54)
int(54)
int(54)
int(54)
int(54)
int(54)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)
int(52)

PHP5.3.15

あんまり分散しない。。。

int(30)
int(52)
int(14)
int(4)
int(32)
int(32)
int(41)
int(17)
int(17)
int(86)
int(26)
int(26)
int(32)
int(32)
int(40)
int(37)
int(37)
int(17)
int(59)
int(59)
int(38)
int(38)
int(92)
int(26)
int(26)
int(21)
int(74)
int(74)
int(13)
int(13)
int(24)
int(12)
int(12)
int(86)
int(88)
int(88)
int(82)
int(92)
int(92)
int(21)
int(21)
int(32)
int(26)
int(26)
int(13)
int(96)
int(96)
int(53)
int(53)
int(65)
int(74)
int(74)
int(21)
int(21)
int(2)
int(52)
int(52)
int(29)
int(45)
int(45)
int(85)
int(85)
int(95)
int(38)
int(38)
int(90)
int(90)
int(90)
int(2)
int(2)
int(38)
int(33)
int(33)
int(96)
int(2)
int(2)
int(32)
int(15)
int(15)
int(7)
int(7)
int(83)
int(16)
int(16)
int(2)
int(50)
int(50)
int(20)
int(20)
int(85)
int(30)
int(30)
int(40)
int(19)
int(19)
int(3)
int(3)
int(13)
int(52)
int(52)
int(95)
int(50)
int(50)
int(79)
int(83)

PHP5.4.12

これは良い。

int(13)
int(62)
int(10)
int(35)
int(60)
int(62)
int(59)
int(20)
int(99)
int(46)
int(88)
int(99)
int(15)
int(75)
int(43)
int(79)
int(47)
int(96)
int(39)
int(28)
int(29)
int(32)
int(22)
int(1)
int(77)
int(70)
int(6)
int(36)
int(74)
int(84)
int(87)
int(65)
int(77)
int(34)
int(15)
int(47)
int(22)
int(84)
int(40)
int(19)
int(8)
int(91)
int(5)
int(67)
int(89)
int(79)
int(40)
int(91)
int(17)
int(95)
int(72)
int(59)
int(14)
int(85)
int(25)
int(50)
int(51)
int(46)
int(18)
int(46)
int(79)
int(73)
int(3)
int(78)
int(17)
int(51)
int(86)
int(19)
int(48)
int(34)
int(41)
int(6)
int(20)
int(71)
int(79)
int(49)
int(30)
int(87)
int(30)
int(57)
int(53)
int(90)
int(43)
int(54)
int(80)
int(46)
int(90)
int(44)
int(33)
int(55)
int(10)
int(9)
int(84)

microtime関数のμタイム部分の精度が悪いのが原因ですなー。たぶん。
でも、PHPのソースコードは差分無かった。なので、実行環境の違いかも知れないなー。

追記

PHPでは乱数発生に置いて以下のような問題がある。参考記事

 PHPにおける乱数発生の実装は、長らく問題とされてきました。例えば、以下のような問題です。

  • メルセンヌツイスタ(1996年に発表された疑似乱数発生器)がいまだに利用されている
  • 状態がグローバルな領域に保持されている(メルセンヌツイスタの利用に由来)
  • 奇数ばかりが出るなどランダム性が不完全である

最後のはやばいような気がするので検証するコードを書いてみましたが、顕著ではないのかもしれません。

<?php
$result = [
0 => 0,
1 => 0
];
for ($i = 0 ; $i < 100000000 ; $i++) {
$rand_number = mt_rand();
$result[$rand_number%2]++;
}
print_r($result);

乱数生成機に偏りがあるとなると、プログラム上で表現されるバランスにクリティカルな影響が出てしまいます。特に、ゲームやカジノなどの確率がビジネスロジックの根底の要素として存在する場合。

PHPの乱数処理に関してより詳細な記事がありました。

PHP の乱数実装がグダグダな話

参考までに、GPTの回答。

コメント