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 とか知らなかったので調べた。

floor
〜以下で最大の整数:
ceil
〜以上で最小の整数

らしい。単純に切り下げ・切り上げで考えてると負の時痛い目みれるそうだ。

POSIX

$ 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 のためにウマく纏められると良かったんだけど、何せ色々と素養が足りないもので……スンマヘン。あー、眠くてオチも思いつきません。ごめんなさい。