この記事は PHP Advent Calendar 2022 14日目の記事です。
IEEE 754
みなさまは、IEEE 754
についてご存知でしょうか?平たくいうと「コンピューターにおける浮動小数点の計算」に関する標準です。
https://ieeexplore.ieee.org/document/4610935
いやいや、そんなの使ったこと無いし知らないよという気持ちかもしれませんが、実は PHP も IEEE 754
に準拠しています。
浮動小数点数の精度は有限です。 システムに依存しますが、PHP は通常 IEEE 754 倍精度フォーマットを使います。 この形式は、1.11e-16 のオーダーでの丸め処理で誤差が発生します。 複雑な算術演算をすると、誤差はさらに大きくなるでしょう。そしてもちろん、 いくつかの演算を組み合わせる場合にも誤差を考慮しなければなりません。
つまり、PHP において、小数点の演算を行うときは、知らず知らずのうちに IEEE 754
準拠の計算をしているということになります。
論よりコード
以下の計算ロジックは、0.01 を 10000 回足しています。普通に考えると答えは 100 になるはずですが…
1 |
|
答えは float(100.000000000001425)
になります。
これは、2進数において表現できない10進数にたいしては 近似計算
による誤差が発生するためです。誤差自体は小さいのですが、10進数で計算した場合とは結果が異なります。
正しい解決方法
PHP には BCMath という任意精度数学関数が用意されています。これを使うことで、上記の小数点演算を誤差なく行うことが出来ます。
1 |
|
答えは string(6) "100.00"
になります。 BCMath の都合上、文字列で数値を表現することになりますが、期待通りの計算をすることが出来ました。
実際に、現場で BCMath を利用する場合は、小数点を表す値クラスを作るなどの工夫をすると、安全に扱えるようになるかと思います。
小数点演算については、もっと色々あるのですが、2023 の PHPerKaigi にもちょうどよいトークがあるようなので、詳しくはそちらも見に行っていただけると良いかと思います。
PHPで任意精度演算を行って「正しい」金額計算をする方法 by 山岡広幸 | プロポーザル | PHPerKaigi 2023 #phperkaigi - fortee.jp
PHP のイシュー
今回、浮動小数点演算を題材にしたのは、PHP のイシューとして浮動小数点演算の誤差に関する話題が定期的に上がってくるためです。
https://github.com/php/php-src/issues?q=IEEE+754+is%3Aclosed+label%3A%22Status%3A+Invalid%22
報告者の方々は、誤差がでることを不具合と思って報告されているのですが、実際はコンピューターの制約による仕様なので、特に対応されずにクローズされます。
PHP は初心者向けの言語として、毎年どんどん新しい人が開発者として加わってきます。中には計算機科学などのバックグラウンドを持たない人もたくさんいらっしゃるので、このようなイシューが上がること自体は無理もないと思います。
PHP コミュニティの方々は、そのような初心者の方々に向けても、丁寧にガイドしてくれていて、PHP マニュアルにもきちんと説明が書かれています。
PHPer として出来ること
私達も、PHP 開発者の端くれとして、コミュニティの一員の意識をもって、こうした知見のガイドをやっていきましょう。今回は、その一例として IEEE 754
を取り上げました。
このほかにも、私達が常識と思っているけど、一般的には常識じゃないという事柄がたくさんあると思います。皆様に置かれましても、コミュニティの底上げをしてみんなでハッピーになるんや!の気持ちで、OSS に関わっていきましょう!考えてみると Advent Calender も、そうした取組の一つなのかもしれませんね。素敵ですね。