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プログラマも混乱しなくて済みます。