Help us understand the problem. What is going on with this article?

分数(Rational)から、その分数が整数になるかどうかを調べる方法

More than 3 years have passed since last update.

例えば、Rubyにおいて分数を扱う際、Rationalというクラスを使う。例えば、3分の1ならばRational(1, 3)といったように書ける。

そこで、3分の2と2分の3は実質1であるわけなんだけれども、この分数同士、あるいは整数と分数のかけ算を行なったさい、それが整数になっているかどうかを調べる必要があったため、そのようなチェックするためのメソッドを用意していたら、罠にはまったので、その罠の詳細について書いておく。

素朴だが悪い方法

素朴な方法としては、一端分数を整数と浮動小数点にし、それぞれを比較してみればよい。もし、分数が割りきれないとするならば、小数点となるはずだし、もし割りきれるならば、結果は整数となるはずである。なので、次のようなメソッドを用意するといいだろう。

def integer? n
  n.to_i == n.to_f
end

puts integer?(Rational(1, 3)) # => false
puts integer?(Rational(2, 3) * Rational(3, 2)) # true

確かに、この方法は判定が素朴ではあるが、しかしFloatオーバーフローする可能性が出てくる。というのも、RubyのFloatC言語におけるdoubleになっているからである。先のリンクの指摘のコードを実行してみると:

Rational(47345680087890625,33).to_f
# => 1.434717578420928e+15

となり、仮数部分が溢れて正しい結果とならない。そのため、他の方法を試す必要がある。

分母を見る方法

分数の場合、それが整数になるならば、約分しきってしまえば、分母は1になるはずである。そこで、分母を取得する方法があれば良い。その方法として、denominatorというメソッドがある。これは、RationalFixnumなどのクラスにおいて、分母を返すメソッドである。

(Rational(3, 2) * Rational(2, 3))
# => 1 / 1 

Rubyの場合、自動的に約分しきってくれるので、もし分母が1ならば、自動的にそれは整数になることができると見なすことができる。なので、次のようなメソッドにすればよい。

def integer? n
  n.denominator == 1
end

そもそもFixnumとRationalは比較できる

上記のことを公開したら、元々Fixnum(Integer)とRationalは比較可能であるという指摘をいただいた。実際、下のようなコードで正常に動くことを確認している:

def integer?
  self.to_i == self
end

Special Thanks

このエントリは下のやりとりから生まれました。指摘して頂いた方々、どうもありがとうございました。

esehara@github
本物のプログラマーではないほうを担当しています
http://bugrammer.hateblo.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away