end0tknr's kipple - 新web写経開発

http://d.hatena.ne.jp/end0tknr/ から移転しました

perlで四捨五入

sprintf() による四捨五入は避けるべきでは

http://d.hatena.ne.jp/perlcodesample/20080922/1222147231

「サンプルコードによるPerl入門」は、よく参考にしていますが、sprintf() による四捨五入には疑問に思うことがありました。「四捨五入を行うには、sprintf関数を使用します」と紹介しておきながら、次のようなことも書かれています。

数値リテラルで表現した0.725は、内部的には0.725ではない為、
sprintf( "%.2f", $num2)は 0.73 にはなりません。

次のようなscriptで実験を行ってみても、確かに sprintf( "%.2f", $num2)は 0.73 にはなりません。

#!perl
use strict;
use warnings;

for my $val (qw/0.723 0.725 0.727/){
    print "Round $val ->",sprintf( "%.2f", $val),"\n";
}
$ ./test.pl 
Round 0.723 ->0.72
Round 0.725 ->0.72
Round 0.727 ->0.73

しかし、本来、sprintf()はFORMAT指定による整形済み文字列を返すものなので、四捨五入は避けるべきではないでしょうか?

わざわざ int()で四捨五入methodは書きたくない

また、次のurlでは sprintf()やint()によって「$num を四捨五入して小数点以下 $decimals桁にする」を紹介していますが、四捨五入の為にここまで書きたくはありません。

http://www.din.or.jp/~ohzaki/perl.htm#NumberRound

sub round {
  my ($num, $decimals) = @_;
  my ($format, $magic);
  $format = '%.' . $decimals . 'f';
  $magic = ($num > 0) ? 0.5 : -0.5;
  sprintf($format, int(($num * (10 ** $decimals)) + $magic) /
                   (10 ** $decimals));
}

更にint()による四捨五入を紹介しているサイトをよく見かけますが、int()には脆弱性もあるようです。

    my $int_val = int($float_val + 0.5);

http://harapeko.asablo.jp/blog/2006/11/27/972082

私の場合、Math::Round::nearest を使っています

私の場合、数値演算にはMath系モジュールをよく使用しますが、その中でも四捨五入には Math::Round::nearest を使っています。

http://search.cpan.org/perldoc?Math::Round

#!perl
use strict;
use warnings;
use Math::Round qw/nearest/;

for my $val (qw/0.723 0.725 0.727/){
    print "Round $val ->",nearest(0.01,$val),"\n";
}
$ ./foo.pl 
Round 0.723 ->0.72
Round 0.725 ->0.73
Round 0.727 ->0.73

Math::Roundは標準モジュールではありませんが、私の場合、四捨五入を必要とする機会が多くないので、これで十分です。