サンプル・プログラム
循環小数による演算誤差
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
プログラムを実行すると、11行目の演算結果は 0.333333....(循環小数)となるため、結果は切り捨てになってしまう。これは表現上の問題なのでやむを得ない。商に 3 を乗算してやれば、1 に戻るので、演算誤差はないといえる。
ところが、単純な乗算プログラムなのに演算誤差が発生するケースがある。期待する結果は 702100 であるはずなのに、702099 と表示されてしまう。
だが、奇妙なことに、printf 関数の %f 修飾子(小数表示)を利用すると、702100.000000 と表示される。整数化の際に誤差が発生しているのだ。
ところが、単純な乗算プログラムなのに演算誤差が発生するケースがある。期待する結果は 702100 であるはずなのに、702099 と表示されてしまう。
だが、奇妙なことに、printf 関数の %f 修飾子(小数表示)を利用すると、702100.000000 と表示される。整数化の際に誤差が発生しているのだ。
BCMath任意精度数学関数
PHPには、BCMath任意精度数学関数という関数群が用意されている。これは、10進数表記された数字に対して任意精度の演算を提供するものである。
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 は万能ではなく、用途に応じた丸め関数を用意することになるだろう。そのときも、ここで定義した bcround 関数のコーディング方法が役に立つと思う。
これで、期待通りの数値を得ることができるようになった。実際のプログラミングの場面では、bcround は万能ではなく、用途に応じた丸め関数を用意することになるだろう。そのときも、ここで定義した bcround 関数のコーディング方法が役に立つと思う。
参考サイト
- 有限小数、循環小数、分数、無理数:ぱふぅ家のホームページ
- PHPで2進数の循環小数かどうか判定する:ぱふぅ家のホームページ
- PHPで分数が有限小数かどうか判定する:ぱふぅ家のホームページ
(この項おわり)
(2021年2月6日)PHP8対応