※注記
のちに全面的に書き直しました。
割り算をするとき、場合によっては割り切れず、小数部分が循環して無限に続くことがあります。これが「循環小数(recurring decimal)」です。これを扱うメソッドを作ってみました。
Rational クラスのインスタンス・メソッド Rational#to_rec_decimal です。String へ変換します。 循環小数でない場合は整数または小数を String で返します。
Rational#rec_decimal? はオブジェクトが循環小数に変換されるなら true、それ以外は false を返します。
いずれも負数をサポートします。
Rational#abs はオブジェクトの絶対値を与えます。Rational#no_minus? はオブジェクトが 0 または正ならば true を、そうでなければ false を返します。これらはおまけです。
class Rational def to_rec_decimal st = main(self.abs) self.no_minus? ? st : "-" + st end def rec_decimal? slf = self.abs @nume = slf.numerator @deno = slf.denominator return false if @deno == 1 get_rcycle.size == 1 ? false : true end def abs self.no_minus? ? self : self * (-1) end def no_minus? self.numerator >= 0 end private def main(slf) @nume = slf.numerator @deno = slf.denominator return "#{@nume}" if @deno == 1 n = @nume / @deno ar = get_rcycle repetition_num = {} s = {} return "#{n}.#{ar[0][1..-1].join}" if ar.size == 1 idx = repetition_num[@deno] = ar[1].size - ar[2] output_st(ar, idx, n) end def get_rcycle quotient = [] remainder = [] divided = @nume - (@nume / @deno) * @deno loop do quotient << divided / @deno a = divided % @deno return [quotient] if a.zero? if (b = remainder.index(a)) return [quotient, remainder, b] end remainder << a divided = a * 10 end end def output_st(ar, idx, n) st = ar[0].join[1..-1] if (ln = st.length) > idx idx = ln - idx "#{n}.#{st[0..(idx - 1)]}(#{st[idx..-1]})" else "#{n}.(#{st})" end end end
実行例と結果。
for i in 1..20 a = Rational(10, i) puts "#{10}/#{i} = " + a.to_rec_decimal end
10/1 = 10 10/2 = 5 10/3 = 3.(3) 10/4 = 2.5 10/5 = 2 10/6 = 1.(6) 10/7 = 1.(428571) 10/8 = 1.25 10/9 = 1.(1) 10/10 = 1 10/11 = 0.(90) 10/12 = 0.8(3) 10/13 = 0.(769230) 10/14 = 0.(714285) 10/15 = 0.(6) 10/16 = 0.625 10/17 = 0.(5882352941176470) 10/18 = 0.(5) 10/19 = 0.(526315789473684210) 10/20 = 0.5
循環小数を分数に直すメソッドも作った
String#to_r をオーバーライドしました。上の書式の循環小数を、Rational オブジェクトに直します。循環小数でなければ普通に今までの String#to_r を実行します。
class String alias :__to_r__ :to_r def to_r s = self sign = 1 if s[0] == "-" sign = - 1 s = s[1..-1] end m = /(\d+)\.(\d*)\((\d+)\)$/.match(s) unless m __to_r__ else a = (m[1] + "." + m[2]).__to_r__ b = ((m[1] + m[2] + m[3]).to_i * 10 ** (- m[2].length)).to_r (sign * (b - a) / (10 ** m[3].length - 1)).to_r end end protected :__to_r__ end
実行例。
irb(main):014:0> "0".to_r => (0/1) irb(main):015:0> "12".to_r => (12/1) irb(main):016:0> "12.2".to_r => (61/5) irb(main):017:0> "12.2(325)".to_r => (122203/9990) irb(main):018:0> "0.(5882352941176470)".to_r => (10/17) irb(main):019:0> "3.(3)".to_r => (10/3) irb(main):020:0> "1.(428571)".to_r + "0.(714285)".to_r => (15/7)
サンプルです。Rational#to_rec_decimal と String#to_r が正しく動いていれば、エラーは出ない筈です。
rn = Random.new begin a = (rn.rand * 1000).to_i b = (rn.rand * 1000).to_i + 1 sign = rn.rand < 0.5 ? -1 : 1 r = Rational(sign * a, b) print "\e[2J\e[1;1H\e[1G#{r} = #{r.to_rec_decimal}\n#{r.rec_decimal?}" sleep(1) end while r.to_rec_decimal.to_r == r puts "\nerror"
Rational#to_rec_decimal と String#to_r とを組み合わせれば、循環小数どうしの厳密な計算が可能になります。
p ("10.(952)".to_r + "5.26(3)".to_r).to_rec_decimal #=>"16.21(628)"