microtime の罠

こんなコードを書いていると痛い目にあいますよという話。もとい、痛い目にあった話。

問題

こんなコードというのは次のようなコード。

<?php
list($sec, $msec) = explode('.', microtime(true));

microtime() の引数に true を渡すと、float 型の値が返ってくる。整数部と小数部をピリオドで分割しようとしているコード。これを実行するとエラーが発生したりしなかったりする。エラーの内容は "PHP Notice: Undefined offset: 1 ..."。

検証

試した PHP のバージョンは次の通り。

% php -v
PHP 5.3.10 with Suhosin-Patch (cli) (built: Feb 20 2012 22:55:53)
Copyright (c) 1997-2012 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2012 Zend Technologies

エラーメッセージから、どういった現象が発生しているかは想像がつくとけれど、それが正しいのか検証してみた。検証コードは次の通り。

<?php
for ($i = 0; $i < 1000000; $i++) {
  $mt = microtime(true);
  if (strstr($mt, '.') === false) {
    echo $mt, PHP_EOL;
  }
}

以下はスクリプトの実行結果。

% php mictortime.php
1344861688
1344861688
...

1マイクロ秒の間にループが何回も繰り返されてるようで、同じ値が 20 行くらい出力される。予想通り、ピリオドなしの値が返ってきた。

解決

sprintf() を使って小数点以下何桁かは必ず出力するようにすれば上記のようなエラーの発生は防げる。

<?php
$mt = sprintf('%.04f', microtime(true));
list($sec, $msec) = explode('.', $mt);

そもそも true を渡さなければ問題は起こらないので、true を渡さないというのでもいいかも。