これは何?
を拝見し、へー PHP には bcadd
bcmul
とかいう関数があるんだへーと思い。
動作確認したら想像と違っていたのでその記録。
BC Math の特徴
bcadd の公式ドキュメントらしきもの を見ると分かる通り、 bcadd
などの引数は文字列になっている。return value も文字列。
そうでないと困る。
気になったこと
引数は文字列にする必要がある
公式でない文書にはよく
<?php
echo bcadd(2.34, 4.56, 3);
のように、 bcadd
に浮動小数点数を渡してる例があるが、これは bcadd
を作った人の意に反する。
どれぐらい意に反するかというと、
<?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 ではない)、これは
-
0.00001
をbcadd
に渡そうとする -
bcadd
は文字列を要求しているので0.00001
を文字列に変換する -
0.00001
は、文字列にすると"1.0E-5"
になる -
bcadd
は「E
が入っている文字列は食べられません」ということでエラー。
ということ。浮動小数点数を渡されることは全く考慮されていないことがわかる。
あるいは。
<?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
$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
$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 を触る可能性はほとんどないのでひとごとだけれども。