2005-12-12
■[Perl] 整数の除算 
ショック!
$ perl -e 'print 10 / 3, "\n"' 3.33333333333333
あうあう、知らなかった……。私ぁ今までどうやって生きてきたんだろう。
で、3 を得るには、正規表現置換かなんかで "." 以降削るのが良いの?
もっとスマートな方法はないべか?
$ perl -e ' my $n = 10 / 3; print $n, "\n"; $n =~ s/\..*$//; print $n, "\n"; ' 3.33333333333333 3
これってダサイよねぇ。
■[Perl] Perl で整数の除算ツアー (Re: 整数の除算) 
皆さんよってたかってありがとうございます。お礼といっては何ですが全て試してみてみます。
まずは、一番メジャーな方法らしい。というか、やってたけど忘れてた。
int
$ perl -e 'print int(10 / 3)' 3 $ perl -Minteger -e 'print 10 / 3' 3
おっふおっふ(鳴き声) 'use integer;' なんてあるのか、ちょっと興奮。
しかし桁溢れると弱い。
$ perl -e 'print int(-6.725/0.025)' -268
「わかってる分には、簡単なの使ったらええやろ」って感じが なかだ さんっぽい。
せっかくなのでドキュメントも読んどくか。
$ perldoc -f int
int EXPR
int Returns the integer portion of EXPR. If EXPR is omitted, uses
$_. You should not use this function for rounding: one because
it truncates towards 0, and two because machine representations
of floating point numbers can sometimes produce counterintu-
itive results. For example, "int(-6.725/0.025)" produces -268
rather than the correct -269; that's because it's really more
like -268.99999999999994315658 instead. Usually, the
"sprintf", "printf", or the "POSIX::floor" and "POSIX::ceil"
functions will serve you better than will int().
ふむ、通常は int より sprintf, printf, POSIX::floor, POSIX::ceil を使えとな。
順に追ってみよう。
sprintf
$ perl -e 'print sprintf("%d", -6.725/0.025)'
-268
これ、ひょっとして int と一緒じゃね? ソースまで見に行かないけど。
恥ずかしながら、floor とか ceil とか知らなかったので調べた。
らしい。単純に切り下げ・切り上げで考えてると負の時痛い目みれるそうだ。
$ perl -MPOSIX -e 'print POSIX::floor(10 / 3)' 3 $ perl -MPOSIX -e 'print POSIX::floor(-10 / 3)' -4 $ perl -MPOSIX -e 'print POSIX::ceil(10 / 3)' 4 $ perl -MPOSIX -e 'print POSIX::ceil(-10 / 3)' -3
id:kidd-number5 さんご紹介の perlfaq4 もこれと同じ「POSIX 使え」ですね。
POSIX 族は名前からして C ライブラリを呼び出してるっぽいから、double の範囲を越えると id:lyokato さんのいうように精度の問題が出そう。場合によるな。四捨五入はないみたい。別に Math::Round なんてあるみたいだけれど。
id:lyokato さんから Math::BigFloat 版。これが一番確実。
$ perl -MMath::BigFloat -e 'print Math::BigFloat->new(-6.725/0.025)->bfloor' -269
でかい数値用の floor。
$ perl -MMath::BigFloat -e 'print Math::BigFloat->new(-6.725/0.025)->bceil' -269
でかい数値用の ceil。
$ perl -MMath::BigFloat -e 'print Math::BigFloat->new(-6.725/0.025)->bfround(1)' -269
小数点第1位で四捨五入。Math::BigFloat には四捨五入もしっかりあるんですな。
id:tokuhirom さんご紹介のにぽたん研究所から正規表現版
$ perl -e ' (my $d = -6.725/0.025) =~ s/^([-+]?\d+)(?:\.\d+)?$/$1/ee; print $d; ' -269
ハッカー臭くてカコエぇー。プロダクションコードなんでやりませんが。といいつつ私には一番わかりやすかったり。バッサリスッキリ。
今、動いてるコードは正規表現版(わはぁー)ですが int に改めようと思います。引いてくる元のデータベースの該当カラムが int(1) なので 0 から 9 までしかありえないから大丈夫でしょう。求めたいものは C や Ruby で言うところの "(n + 1) / 2" です。
+-----+---+---+---+---+---+---+---+---+---+ | var | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | +-----+---+---+---+---+---+---+---+---+---+ | ans | 1 | 1 | 2 | 2 | 3 | 3 | 4 | 4 | 5 | +-----+---+---+---+---+---+---+---+---+---+
(0 の場合は「あってたまるか、元データが悪い」という態度)
後続 Perler のためにウマく纏められると良かったんだけど、何せ色々と素養が足りないもので……スンマヘン。あー、眠くてオチも思いつきません。ごめんなさい。
# kidd-number5 『はじめまして
これかなあ・・・。
http://perldoc.perl.org/perlfaq4.html#Does-Perl-have-a-round()-function%3f--What-about-ceil()-and-floor()%3f--Trig-functions%3f』
# lyokato 『こんにちは。
Math::BigFloat->new( 10 / 3 )->bfround(1)
ってのはどうでしょう。』
# lyokato 『あー、四捨五入じゃないんですね
->bfround(1)じゃなくて->bfloor()ですかね
POSIX::floor とかは精度の問題があるので
でかい数値使うときは怖いです。』
# なかだ 『int(10/3)』
# なかだ 『あるいは use integer;』
# tokuhirom 『http://blog.livedoor.jp/nipotan/archives/18935329.html』
# kdmsnr 『最近ハテナオヤメソッドになってる』
# babie 『いかんなぁ、妄想度を上げなきゃ。』