サンプル・プログラム
循環小数による演算誤差
marginerror.php
9: //循環小数
10: $a = 1;
11: $b = 3;
12: $x = $a / $b;
13: printf("%s÷%s=%f<br />\n", $a, $b, $x);
14: $x = $x * $b;
15: printf("%s÷%s×%s=%f<br />\n", $a, $b, $b, $x);
16:
17: //小数乗算
18: $a = 10000;
19: $b = 70.21;
20: $x = $a * $b;
21: printf("%s×%s=%d<br />", $a, $b, $x);
22: printf("%s×%s=%f<br />", $a, $b, $x);
23:
24: /*
1÷3=0.333333
1÷3×3=1.000000
10000×70.21=702099
10000×70.21=702100.000000

ところが、単純な乗算プログラムなのに演算誤差が発生するケースがある。期待する結果は 702100 であるはずなのに、702099 と表示されてしまう。
だが、奇妙なことに、printf 関数の %f 修飾子(小数表示)を利用すると、702100.000000 と表示される。整数化の際に誤差が発生しているのだ。
BCMath任意精度数学関数
marginerror2.php
9: bcscale(10); //有効桁数を10桁にする
10: //小数乗算
11: $a = 10000;
12: $b = 70.21;
13: $x = bcmul($a, $b);
14: printf("%s×%s=%d<br />", $a, $b, $x);
15:
16: //循環小数
17: $a = 1;
18: $b = 3;
19: $x = bcdiv($a, $b);
20: printf("%s÷%s=%f<br />\n", $a, $b, $x);
21: $x = bcmul($x, $b);
22: printf("%s÷%s×%d=%d<br />\n", $a, $b, $b, $x);
23:
24: /*
10000×70.21=702100
1÷3=0.333333
1÷3×3=0
任意精度で四捨五入
1÷3=0.333333
1÷3×3=1
これで、期待通りの数値を得ることができるようになった。実際のプログラミングの場面では、bcround は万能ではなく、用途に応じた丸め関数を用意することになるだろう。そのときも、ここで定義した bcround 関数のコーディング方法が役に立つと思う。
参考サイト
- 有限小数、循環小数、分数、無理数:ぱふぅ家のホームページ
- PHPで2進数の循環小数かどうか判定する:ぱふぅ家のホームページ
- PHPで分数が有限小数かどうか判定する:ぱふぅ家のホームページ
(2021年2月6日)PHP8対応