四捨五入でも int の使用は避けるべきでは。。。? ― 2006年11月27日 16時23分33秒
相も変わらず Perl のお話。
「perl 四捨五入」でぐぐると、大抵のサイトで以下の方法が紹介されているわけなんだけれども、
$int_value = int($float_value + 0.5);
その一方で、ラクダ本にはこの int
関数の脆弱性が示されている。以下のコードを実行すると、
$\ = "\n"; print 6.725 / 0.025; print int(6.725 / 0.025);
86 系のプロセッサであれば、OS に関係なく、恐らく以下のように出力されるんではないかと思う。
269 268
野暮ったい説明は好きじゃないので、何でか知りたい人は以下のコードを試して欲しい。あるいは素直にラクダ本買ってくれ。
$\ = "\n"; print 6.725 / 0.025; print sprintf '%.20f', 6.725 / 0.025; # この値の正体を、とくとご覧あれ print int(6.725 / 0.025);
で、おんなじようなことは四捨五入でも起こりうるんじゃないかと思って試してみたらやっぱり起こったわけですよ。以下、実証コード。
$\ = "\n"; print 6.725 / 0.05; print int(6.725 / 0.05 + 0.5);
結果は以下の通り。
134.5 134
で、当のラクダ本では何て書いてあるのかというと、こんな風に書いてある (近藤氏による訳をそのまま引用)。
たいていの場合、
sprintf
、printf
、またはPOSIX::floor
やPOSIX::ceil
関数を使ったほうが、int
よりも良い結果をもたらすだろう。
POSIX モジュールはありがたいことに Win32 版の ActivePerl にも付属していて、ちゃんと動作する。そこで、Windows XP 環境にて、以下のサンプルを用いて実際の動作を試してみることにする。
use POSIX qw(floor); $\ = "\n"; $var = 6.725 / 0.025; print $var; print int $var; print floor($var); print sprintf '%d', $var; print sprintf '%d', "$var";
結果は以下の通り。
269 268 268 268 269
sprintf
関数に、変数 $var
を文字列コンテキストで渡した場合のみ、期待通りに動いてくれた。もちろん四捨五入のケースでも同様。
use POSIX qw(floor); $\ = "\n"; $var = 6.725 / 0.05 + 0.5; print $var; print int $var; print floor($var); print sprintf '%d', $var; print sprintf '%d', "$var";
135 134 134 134 135
この辺、十分に検証せずにコードを書いてしまうと、信頼性の低いシステムを組んでしまうことにもなりかねないので、注意が必要なんではないかと思う。
つか、そもそもこういう計算で正確な概数を求めたいケースってのは事務仕事に限られてくるわけで、最初から内部表現は整数だけにして誤差が起こりえないような設計にしろよって話になりそうな気もするが。例えば 0.025 で割る替わりに、40 で掛けてみるとか。。。
1.15。。。そんなあっさり再現するとは。。。\(^O^)/
ちなみに、上で書いたように文字列コンテキストで渡しても無駄っぽいです。当たり前か、数値コンテキストに変換されるわけだから。
$n = 1.15; printf("%.1f\n", $n); printf("%.1f\n", "$n");
1.1 1.1
Math::Round
はその辺うまいこと解決してくれるのかなぁ。。。やってみたいけど、でも今標準モジュールじゃないものを入れてしまいたくはないしなぁ。。。 (個人的事情により)
コメント
トラックバック
このエントリのトラックバックURL: http://harapeko.asablo.jp/blog/2006/11/27/972082/tb
※なお、送られたトラックバックはブログの管理者が確認するまで公開されません。
コメントをどうぞ
※メールアドレスとURLの入力は必須ではありません。 入力されたメールアドレスは記事に反映されず、ブログの管理者のみが参照できます。
※投稿には管理者が設定した質問に答える必要があります。