餡子付゛録゛

ソフトウェア開発ツールの便利な使い方を紹介。

PHPの設定オプションprecisionが17以上のときの挙動

id:hnw氏が以前のエントリのコメントで「PHPの新しいround関数にバグをみつけた」の詳しく説明してくれました。
なるほどなるほど。私は以下の理由で勘違いをしていたようです。

    • JavaのFormatNumberも17桁以上の有効桁数は取らない
    • new BigDecimal("1000000000000000.125").precision()が19を返す

IEEE754範囲外という前のエントリは間違いでした。でも、PHPの中の人は勘違いをしていないかも知れません。

id:hnw氏のバグ報告の整理

id:hnw氏の主張を整理してみましょう。1000000000000000.125のビット表記は以下のようになり、IEEE 754形式に収まると言う事のようです。

10進数 1000000000000000.125
符号 0
指数 10000110000
仮数 1100011010111111010100100110001101000000000000000001

round()関数を使って四捨五入後すると得られるはずの1000000000000000は、以下のようになります。IEE754形式に収まります。

10進数 1000000000000000
符号 0
指数 10000110000
仮数 1100011010111111010100100110001101000000000000000000

PHPはIEEE754の倍精度浮動小数点をサポートしているため、ini_set();で規定したprecisionの値に関わらず、上のような挙動をすべきだと言う事です。
2進数で数字を出して、何となく納得しました。

問題はround()関数ではなく、ini_set("precision",19);時の挙動

しかし、やはりini_set("precision", 19);は問題を残している気がします。有効桁数の問題から考えると、プログラマから見て以下のような一貫しない挙動になります。

    • 9007199254740993は、10進数で16桁で、IEEE754の倍精度の範囲外
    • 1000000000000000.125は、10進数で19桁で、IEEE754の倍精度の範囲内

有効桁数が19桁だと思っているプログラマが、16桁の数字で正しく計算ができない事に悩まされるわけです。
id:hnw氏が期待している事は、IEEE754の範囲内の演算は設定オプションprecisionに関わらず正しく演算し、文字列に変換時だけprecisionを参照しろと言うことだと思います。
しかし、precisionが小さい挙動であれば上述の仕様で問題ないわけですが、precisionが17(16?)を超えたところから、precisionによる精度とIEEE 754の精度の差が出てします。
私は17(もしくは15)以上のprecisionが不正値と、PHPの仕様を定義すればいいと思います。そうすれば現在のround()関数の挙動も問題ないですし、私も^H^Hプログラマも混乱しなくて済みます。