hnwの日記

PHPで整数の範囲を超えたときの挙動について


(4/17追記twitter経由でタレコミがありまして、恥ずかしいミスをコッソリ直しました(32bit整数の範囲について)。どなたかわかりませんが、ありがとうございます!


PHPの整数(integer型)はCのlong型をそのまま利用しています。つまり、32bit環境であれば整数の範囲は-2^31から2^31-1までとなります。また、PHP 4.4.0以降および5.0.5以降、最大の整数を表す定数PHP_INT_MAXが利用できます。


ところで、最大の整数に1を足すと何が起こるのでしょうか。PHPマニュアルの「整数のオーバーフロー」にも記述がありますが、整数の範囲を超えるような演算の結果はfloat型となります。

<?php
var_dump(PHP_INT_MAX); // int(2147483647)
var_dump(PHP_INT_MAX+1); // float(2147483648)


そのため、整数の最大値や最小値の付近では一見不思議な現象が起こります。

<?php
var_dump(PHP_INT_MAX == PHP_INT_MAX+1-1); // bool(true)
var_dump(PHP_INT_MAX === PHP_INT_MAX+1-1); // bool(false)
var_dump(PHP_INT_MAX === PHP_INT_MAX-1+1); // bool(true)


1行目と2行目は、最大の整数に1足して1を引いて元の数と同じかどうかを確認しています。1つめの例では==が成立していますが、2番目のように===で比較すると両者は等しくありません。これは、最大の整数に1を足した時点でfloatになってしまうため、型が異ってしまうためです。1つ目の例で==が成立するのは、両者をfloatにキャストして比較しているためです。


3番目の例のように、-1して+1すれば計算途中でintegerの範囲を超えないので、===が成り立ちます。比較に===を使う場合には注意したいポイントです。

64bit環境での挙動

これまでの説明は32bit環境での結果です。同じことを64bit環境でも実験してみましょう。


64bit環境での整数の範囲は-2^63から2^63-1です。integerの演算結果がintegerの範囲を超えるとfloatになる仕様は64bit環境でも同じですが、32bit環境とは条件が少し変わります。64bit環境では、PHP_INT_MAXは約900京となりますが、この大きさになるとfloat型(IEEE64bit浮動小数点数仮数部は53bit)では全ての整数を正確に扱えないのです。PHP_INT_MAX付近で言うと、floatでピッタリ表現できるのは 2^63-2^11 、 2^63-2^10 、 2^63 、 2^63+2^11 といった数になります。



これは以前の記事「第51回PHP勉強会@関東に参加してきました」で示した数直線です。見てわかるように、32bit環境のPHP_INT_MAX付近ではfloatの方が精度があったのが、64bit環境ではfloatの方が精度が低くなっています。この性質を利用すると、一見不思議に見える例が作れます。

<?php
// 64bit版のPHPバイナリで試してください
$a = PHP_INT_MAX-1;
$b = PHP_INT_MAX;
$c = PHP_INT_MAX+1;

var_dump($a<$b);  // bool(true)
var_dump($b==$c); // bool(true)
var_dump($a==$c); // bool(true)


整数の最大値と、+1および-1した数を比較してみました。2番目と3番目の例で==が成立しているのは、$cにあわせて$a,$bを浮動小数点数にキャストした際に、丸めが発生して同じ数になるためです。32bit環境で同じ比較をすれば$a<$b<$cとなるわけですから、これはPHPのinteger型のサイズがfloat型の仮数部のbit数を超えた弊害と言えるでしょう。


また、これでは推移律が成り立っていませんので、「sort関数と全順序集合」でも解説したのと同じ理由で、==によるsortが少し奇妙な結果を返します。

<?php
// 64bit版のPHPバイナリで試してください
$a = PHP_INT_MAX-1;
$b = PHP_INT_MAX;
$c = PHP_INT_MAX+1;
$array = array($a, $b, $c);
sort($array);
var_dump($array); // array($a, $c, $b);


この挙動のせいで困るアプリケーションが僕には思いつかないので、ちょっと格好悪いな、というレベルの問題のように思っています。

まとめ

  • PHPではinteger型の計算中にintegerの範囲を超えるとfloat型になる
  • 64bit環境ではintegerの範囲を超えると情報量が落ちる
  • 実アプリケーションではあまり問題にならないと思われる