LoginSignup
5
2

More than 1 year has passed since last update.

PHP の BC Math 関数のわかりにくい間違え方

Posted at

これは何?

を拝見し、へー PHP には bcadd bcmul とかいう関数があるんだへーと思い。
動作確認したら想像と違っていたのでその記録。

BC Math の特徴

bcadd の公式ドキュメントらしきもの を見ると分かる通り、 bcadd などの引数は文字列になっている。return value も文字列。
そうでないと困る。

気になったこと

引数は文字列にする必要がある

公式でない文書にはよく

PHP
<?php
echo bcadd(2.34, 4.56, 3);

のように、 bcadd に浮動小数点数を渡してる例があるが、これは bcadd を作った人の意に反する。
どれぐらい意に反するかというと、

PHP
<?php
echo bcadd(0.1, 0.0001, 6)."\n"; #=> 0.100100 が出る。
echo bcadd(0.1, 0.00001, 6)."\n"; #=> エラー
echo bcadd(1, 10000000000000.0, 0)."\n"; #=> 10000000000001 が出る。
echo bcadd(1, 100000000000000.0, 0)."\n"; #=> エラー

これぐらい。
PHPer の人なら分かる通り(なお、私は PHPer ではない)、これは

  1. 0.00001bcadd に渡そうとする
  2. bcadd は文字列を要求しているので 0.00001 を文字列に変換する
  3. 0.00001 は、文字列にすると "1.0E-5" になる
  4. bcadd は「 E が入っている文字列は食べられません」ということでエラー。

ということ。浮動小数点数を渡されることは全く考慮されていないことがわかる。

あるいは。

PHP
<?php
$a = 6.99999999999999;
$b = 7.0;
echo "op - : b-a=".($b-$a)."\n"; #=> op - : b-a=9.7699626167014E-15
echo "bcsub: b-a=".bcsub($b, $a, 20)."\n"; #=> bcsub: b-a=0.00000000000000000000

このように、 bcsub の方が精度が低いように見えてしまうケースもある。
bcsub を正しく使う(引数を文字列にする)と

PHP
<?php
$a = "6.99999999999999";
$b = "7.0";
echo "op - : b-a=".($b-$a)."\n"; #=> op - : b-a=9.7699626167014E-15
echo "bcsub: b-a=".bcsub($b, $a, 20)."\n"; #=> bcsub: b-a=0.00000000000001000000

のように、誤差のない結果が得られる。

floor, ceil は、無い

私が見逃しているのかもしれないけど、BC Math シリーズには四則・非数・平方根・比較 ぐらいで切り上げ切り捨てのような関数はない。

素朴に floor などに突っ込むと暗黙のうちに浮動小数点数への型変換が発生し、下記のように計算誤差が出ることがある。

PHP
<?php
$a = "101010101010101010";
$b = "30303030303030303";
$c = bcadd($a,$b,0);
echo bcmod($c, 1000000, 0)."\n"; #=> 131313 (正しい)
echo (floor($c) % 1000000)."\n"; #=> 131312 (誤差が出ている)

じゃあどうやって切り上げ切り捨てをすればいいんだという話になるわけだけど。

A. 文字列処理で自力実装
B. BC Math 関数を組み合わせて自力実装

の二通りしか無いと思う。

感想

難儀だね。

とはいえ。この手の記事を書く以外の目的で PHP を触る可能性はほとんどないのでひとごとだけれども。

5
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2