四捨五入でも 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

で、当のラクダ本では何て書いてあるのかというと、こんな風に書いてある (近藤氏による訳をそのまま引用)。

たいていの場合、sprintfprintf、または POSIX::floorPOSIX::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 で掛けてみるとか。。。


Thu Nov 30 23:48:42 JST 2006 - 追記

1.15。。。そんなあっさり再現するとは。。。\(^O^)/

ちなみに、上で書いたように文字列コンテキストで渡しても無駄っぽいです。当たり前か、数値コンテキストに変換されるわけだから。

$n = 1.15;
printf("%.1f\n", $n);
printf("%.1f\n", "$n");
1.1
1.1

Math::Round はその辺うまいこと解決してくれるのかなぁ。。。やってみたいけど、でも今標準モジュールじゃないものを入れてしまいたくはないしなぁ。。。 (個人的事情により)

コメント

コメントをどうぞ

※メールアドレスとURLの入力は必須ではありません。 入力されたメールアドレスは記事に反映されず、ブログの管理者のみが参照できます。

※投稿には管理者が設定した質問に答える必要があります。

名前:
メールアドレス:
URL:
次の質問に答えてください:
おいらがやっている会社の名前をひらがな4文字で。

コメント:

トラックバック

このエントリのトラックバックURL: http://harapeko.asablo.jp/blog/2006/11/27/972082/tb

※なお、送られたトラックバックはブログの管理者が確認するまで公開されません。