読者です 読者をやめる 読者になる 読者になる

餡子付゛録゛

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

1000000000000000.125はIEEE754倍精度浮動小数点の範囲内?

id:hnw氏に、延髄反射で早合点するなバーカと言われました。derick氏と同じにされてケシカラン。私は早合点ですが、derick氏は理解した上でBogusにしているのだと思います。

1000000000000000.125と19桁指定がBogusにされた理由

derick氏が早合点では無いバグ報告をBogusにした理由を列挙しますね。

    • id:hnw氏のサンプルは、1000000000000000.125。10進数で19桁。60bits精度。
    • id:hnw氏はini_set("precision",19);で、10進数で19桁精度を指定している。PHPの初期値は14。
    • IEEE 754の倍精度の仮数は、53bits精度。10進数で16桁。指数を0で最大値9007199254740991。
    • id:hnw氏のサンプルが、IEEE 754の倍精度の範囲外であるのは有効桁数が多いときの問題であるのは明白。ゆえにBogus。

以前のバージョンのPHPで、round()が機能した理由

Intel x86プロセッサの倍精度の仮数は、80bits精度。10進数で25桁。指数を0で最大値1208925819614629174706175になります。
後はPHPの内部処理に関わってくるのですが、メモリに保存しない限り、つまりレジスタ演算をしている限りは80bits精度になるようです。特定の浮動小数点を取り扱うと無限ループに陥るPHPのバグは、このレジストとメモリの精度の違いに起因していたそうです。
つまり、PHPのバージョンによっては、60bits精度の浮動小数点に対して、round()が動いたり、動かなかったりするのは当然です。

derick氏の英語を考える

早合点したとも読めなくもないのですが、きっと私ではないので、次のような理由で分かりづらい英語になっているのだと思います。
英語圏ではネガティブな事を書かない習慣なので、「10進数で19桁で、60bits精度だからPHPは精度保証なんてしていないんだから、計算できなくて当然」なんてコメントは返ってきません。「浮動小数点は制限された精度を持ちます」で終了になってしまいます。

Floating point values have a limited precision. Hence a value might not have the same string representation after any processing. Hence a value might not have the same string representation after any processing. That also
includes writing a floating point value in your script and directly printing it without any mathematical operations.
(へっぽこ訳:浮動小数点は制限された精度を持ちます。ゆえに、あらゆる処理後は、値は等しい文字列表現ではありません。これは、id:hnw氏のスクリプトに書かれた浮動小数点と、数学的操作無しに直接表記されたものを含みます。)

しかし、良く読むと、同じ問題はround()関数を通していない部分にもあるよって書いてありますね。

PHP浮動小数点の問題: 17桁以上の精度指定だと、切捨て処理が正しく行われない

前に書いた例で間接的に表記したのですが、問題は、17桁以上の精度設定で、精度外の値の切捨て処理が適切に行われていないと言う事です。
id:hnw氏の1000000000000000.125は1000000000000000として扱われるべきでしょうし、123.00000000000001は、123として扱われるべきでしょう。つまり、丸め処理が行われていないから、round()関数のサポート範囲16桁を超えた値で演算しようとし、後に端数が残るわけです。
ini_set("precision",19)をしてしまうと、IEEE754の精度内へ丸め処理が行われなくなってしまうようですね。PHPの中の人(cataphract氏)は気付いているようで、以下のように言っています。

If anything, the cutoff value is too high:
(へっぽこ訳:どちらかと言えば、切捨て値が高すぎると言う事だね)

precisionの指定が高すぎるとダイレクトに言われないと、ノン・ネイティブには分かりづらいです。
つまり、round()関数の挙動に関してコメントした前回の記事は違う問題だといわれましたが、同じ問題の症状になると思います。