「perlで小数を四捨五入」問題を深追いしてたらやめられないとまらない。
・小数の四捨五入の仕方
http://easycgi.xrea.jp/perltips/round.htm
上記ページに載せられているスクリプトをコピペで動作検証すると、2.345を小数点第三位で四捨五入した場合2.34になります。つまり普通に間違ってます(perl5.8.9で確認)。intを実行した時点(手順2)で$nが内部的に無限小数になるようですが、手順2の次の行で$nをprintすると「見かけ上」補正されるのが罠。手順3の$qは無限小数化してしまっている。
ちなみに、小数点以下を四捨五入するときによく使われるっぽい0.5足してintするという方法も、この事例に合わせてコード化した場合、int((2.345 * 100) + 0.5))/100とかすると、答えが2.34になります。むろん同じように間違ってます。
sprintfがダメだからintでいいというわけではありません。
・perlで四捨五入>sprintf() による四捨五入は避けるべきでは
http://d.hatena.ne.jp/end0tknr/20080928/1222581535
・四捨五入でも int の使用は避けるべきでは。。。?
http://harapeko.asablo.jp/blog/2006/11/27/972082
なんか調べていたらとちょっと怖くなる感じもあった。プロのプログラマっぽい人でも普通にsprintfや0.5足してintする方法を紹介している。事情をわかってて入門者用にわざと大雑把にやってるのかもしれないけど、なんかの計算で小数が混じってたらperlは当たり前のように四捨五入を間違うんだけど、ほんとにsprintfやintを紹介していいんだろうか。小数の四捨五入それ自体は小学4年生位で習うみたいで、大抵の人はできると思うが。
perlが2進数変換にともなって四捨五入を間違う問題は、いずれにせよ、すでにある便利なモジュールを使うのが正しいってわけだけど、実は、このサーバではその便利なMath::Roundとやらは入ってないのであります。いやぁ、それで苦肉の策として、数値を文字列(リテラル)として扱うコードを書いて、モジュールもsprintfもintも使わずに小数を四捨五入してます。たぶんかなり筋の悪い方法ですが、他にあんまり思いつきませんでした。
以下サブルーチンの形でサンプルを出してみました。よく言えば力技、悪く言えば泥縄式。うー!
※小数部分をperlで四捨五入するための泥縄式サンプル
sub DECIMAL_ROUND {
my $num=$_[0]; #処理対象の数
my $round_digit=3; #小数点以下第何位で四捨五入するか(1以上だがあまりに大きすぎるとエラー)
my $plus_minus = 0;
#マイナスの数値の場合の前処理
if ($num < 0) {
$num = abs $num;
$plus_minus = 1;
}
$num = $num * (10 ** $round_digit);#四捨五入する桁以上をすべて整数にする
if ($num =~ /^(\d+)\.\d+$/) {#まだ小数部分がある場合に文字列として切り捨て
$num = $1;
}
if ((length $num) == 1) {#四捨五入する桁しかない場合
if ($num >= 5) {
$num = 1;
}
else {
$num = 0;
}
}
else {#それ以外の場合
my $round_num = chop $num;
if ($round_num >= 5) {
$num++;
}
}
$round_digit--;#四捨五入後の小数点以下の桁数に変換(変数を節約してるだけ)
$num = $num / (10 ** $round_digit);#小数点の位置を元に戻す
#マイナスの数値の場合の後処理
if ($plus_minus == 1) {
$num *= -1;
}
return $num;
}
コメントする