hnwの日記

round関数で整数を四捨五入してみる

今日はPHPのround関数に関して前回と違った切り口で紹介してみます。また、コンピュータ上での整数についても少し紹介してみます。


前回の記事「PHPの奇妙なround関数」([id:hnw:20070515])を読んで、小数点が付いた数なんてPHPで触ったことないから関係ないや、なんて考えた方が居るかもしれません。そんな方のために、今回は整数を四捨五入してみます。


PHPround関数は、省略可能ですが第2引数を取ることもできます。これは、何桁目までで丸めるかの指定です。デフォルトは0です。例えば第2引数に2を指定すると、小数点以下第3位を四捨五入して小数点以下第2位までに丸めてくれます。


ここにマイナスの数を指定することで、1の位より上の桁で四捨五入をすることもできます。たとえば、下記のように第2引数に-2を指定すれば100の位で丸められます。これは仕事でプログラムを書く際にも使える場面がありそうです。

$ php -r '$x=1192;var_dump(round($x, -2));'
float(1200)

さて、このような使い方をしたとき、前回奇妙に感じた件はどう影響するんでしょうか。早速試してみましょう。

$ php -r '$x=149999999999; printf("%16f %16f\n", $x, round($x, -11));';
149999999999.000000 100000000000.000000
$ php -r '$x=150000000000; printf("%16f %16f\n", $x, round($x, -11));';
150000000000.000000 200000000000.000000

約1500億を100億の位で四捨五入してみたところ、正しい結果が得られました。それでは約1.5兆ではどうでしょうか。

$ php -r '$x=1499999999990; printf("%16f %16f\n", $x, round($x, -12));';
1499999999990.000000 1000000000000.000000
$ php -r '$x=1499999999991; printf("%16f %16f\n", $x, round($x, -12));';
1499999999991.000000 2000000000000.000000

予想外の結果になってしまいました。といっても、前回の結果を知っている人にとっては予想通りの結果と言えます。結論としては、前回の記事で見たround関数の奇妙な挙動は整数のときにも関係があるといえます。1000億より上の桁で四捨五入したい場合は整数だからといって油断できません。


念のため他の言語でも試してみることにしましょう。今回はPythonを使ってみます。

$ python -c 'x=1499999999999; print "%16f %16f" % (x, round(x,-12));'
1499999999999.000000 1000000000000.000000
$ python -c 'x=1500000000000; print "%16f %16f" % (x, round(x,-12));'
1500000000000.000000 2000000000000.000000
$ python -c 'x=1499999999999999; print "%16f %16f" % (x, round(x,-15));'
1499999999999999.000000 1000000000000000.000000
$ python -c 'x=1500000000000000; print "%16f %16f" % (x, round(x,-15));'
1500000000000000.000000 2000000000000000.000000

そりゃそうですよね。もはや普通の答が返ってくると物足りない気さえしますね。


蛇足ですが、浮動小数点数が身近では無い人の中には、浮動小数点数のことを何だかアバウトな数だと感じている人が居るのではないでしょうか。浮動小数点数の演算はさまざまな理由で誤差が入りますので、あらゆる場所で不正確なイメージでも無理は無い気がしますけど、「浮動小数点数は数直線上の一部の数しか正確に表せない」「浮動小数点数で正確に表せない値が出現したときに限り誤差が発生する」が正解です。


そして、IEEE64bit浮動小数点数であれば仮数部が52bitありますので、-2の53乗から2の53乗(約9000兆)までの整数は全て正確に表現できます。つまり、今回登場した変数値はどれも見た目通りの整数です*1


最初に書いた通り整数の話題ということで、今回は非常に身近な話題でしたね!


蛇足の蛇足で真面目な話をすると、GNUの任意多倍長整数演算ライブラリGNU MPのインターフェースであるGMP関数PHPにも用意されています。本当に巨大な整数を扱いたいのであれば、これを使うべきでしょう。もしくは、用途によってはBCMath任意精度数学関数の方が扱いが楽かもしれません。僕はどちらも必要になったことがありませんけどね。

*1:整数型ではありませんけど