Ruby で循環小数を扱う

以前にも同様の試みをしたのですが(参照)、コードを始めから書き直しました。以前のは何か自分でもよくわからない、面倒なことをしているので。

作ったのは Rational#to_rec_decimal と String#to_r で、前者は Rational(分数)を(String で表される)循環小数に、後者はその逆で(String で表される)循環小数を Rational(分数)に直します。ともに負数もサポートします。String#to_r は同名の組み込みメソッドをオーバーライドしています。

例。

irb(main):001:0> require './rec_decimal'
=> true
irb(main):002:0> Rational(25, 17).to_rec_decimal
=> "1.(4705882352941176)"
irb(main):003:0> Rational(12, 7).to_rec_decimal
=> "1.(714285)"
irb(main):004:0> Rational(55, 48).to_rec_decimal
=> "1.1458(3)"
irb(main):005:0> "0.23(41)".to_r
=> (1159/4950)
irb(main):006:0> "7.(3)".to_r
=> (22/3)
irb(main):007:0> Rational(-1, 3).to_rec_decimal
=> "-0.(3)"
irb(main):008:0> "-2.45(7)".to_r
=> (-553/225)

 
コード。
rec_decimal.rb

class Rational
  #分数を循環小数に直す
  def to_rec_decimal
    #String{f, result}, Rational{num, ra}
    #Integer{i, remainder, deno, place[], rems[], idx} >> String
    
    f, num = (self < 0) ? ["-", -self] : ["", self]
    
    i = num.to_i
    result = f + i.to_s
    ra = num - i
    return result if ra.zero?
    result += "."
    
    remainder = ra.numerator
    deno      = ra.denominator
    place = []
    rems  = []
    begin
      rems  << remainder
      place << remainder * 10 / deno
      remainder = remainder * 10 % deno
      return result + place.join if remainder.zero?
    end while not (idx = rems.find_index(remainder))
    place.insert(idx, "(")
    result + place.join + ")"
  end
end

class String
  #循環小数を分数に直す
  alias :__to_r__ :to_r
  def to_r
    #String{st}, Rational{result}, m, f, l >> Rational
    st = delete(" ")
    return st.__to_r__ unless (m = /^([^\d]?)(\d+)\.(\d*)\((\d+)\)$/.match(st))
    f = (m[1] == "-") ? -1 : 1
    
    result = (m[2] + "." + m[3]).__to_r__
    l = m[4].length
    result += Rational(m[4].to_i, 10 ** l - 1) * Rational(1, 10) ** m[3].length
    result * f
  end
end

 

Gem 化

いろいろ使っているコードを Gem 化した中に入れておきました。
kaki-utils | RubyGems.org | your community gem host
インストールは $ gem install kaki-utils で。使うときは

require 'kaki/utils/rec_decimal'

でどうぞ。