正木です。

Float が(現在の実装で)表現している数値は

class Float
  def to_Rational(m=53)
    a,e=Math.frexp(self)
    (2**m*a).to_i/2**(m-e)
  end
def

で正確に Rational に変換できます。

しかし [ruby-math:00785] での Rational#to_f と組み合わせると
(1/3).to_f.to_Rational != 1/3

となり、又例えば Float の 3.14 は

(3.14).to_Rational #=> 7070651414971679/2251799813685248

(=3.140000000000000124344978758017532527446746826171875)

で、これが正確な値ですが、実装上の都合でこうなっているだけで
Float を使う人が普通に期待する結果でないことも確かです。

そこで正確な値ではなく期待される値を返す method を考えてみました。
上記の例を連分数展開をして近似分数(と誤差)を求めてみると:

[3, 1/7]
[22/7, 1/350]
[157/50, 1/8042142191732850]
[505046529640823/160842843834657, 1/362185885779486072571152039936]
[7070651414971679/2251799813685248, 0]

この中から Float の誤差内に入る最初の近似分数を選べば良さそうです。
これは Real Class を使えば次のように定義できます:

class Float
  def error(m=53)
    a,e=Math.frexp(self)
    1/2**(m-e)
  end

  def to_rational
    to_Rational.to_Real.approx(error)
  end
end

これだとかなり多数の有理数に対して

a.to_f.to_rational==a

が成り立ちます(例外は例えば a=1/5**24)。 

有効桁数が小さければ
(3.14).to_rational => 157/50
(3.14159265).to_rational => 62831853/2000000
となって期待通りです。有効桁数が大きくなると
(3.141592653).to_rational => 223263031/71066830
となってしまいますが、10桁以上も有効数字を書いた場合は近似値と
解釈されても仕方がないと思うことにします。

この目的のためだけに Real Class を使うのはおおげさすぎるので、
必要最小限の method に絞ったものを付記しておきます:

-------

class Sequence
  def initialize(list=[],*parameter)
    @list=list
    @para=parameter
    @lambda=eval("lambda{"+yield+"}")
  end

  def [](n)
    @list[n] ||= @lambda.call(n,*@para)
  end

  def each_with_index
    i=0
    loop do
      yield(self[i],i)
      i+=1
    end
  end

  def find_with_index
    r=nil;each_with_index{|x,i| if yield(x,i) then r=[x,i];break;end};r
  end

  def abs
    x=self
    Sequence.new([],x){"|n,x| x[n].abs"}
  end

  def delta
    x=self
    Sequence.new([],x){"|n,x| x[n+1]-x[n] "}
  end

  def CFraction(list)
    s=self
    Sequence(list,s){"|k,s| self[k-1]*s[k-2]+self[k-2] rescue self[k-1] "}
  end

  def ContFraction_to_ApproxFractions
    a=CFraction([0,1])
    b=CFraction([1,0])
    Sequence([],a,b){"|k,a,b| (a[k+2]&&b[k+2])? a[k+2]/b[k+2]:self[k-1]  "}
  end
end

class Real  < Numeric
  def initialize(s,e)
    @seq=s
    @error=e
  end

  def [](n)
    @seq[n]
  end

  def find_index
    @error.find_with_index{|e,i| yield(e)}[1]
  end

  def approx(eps)
    self[find_index{|e| e <= eps }]
  end
end

class Object
  def Sequence(list=[],*parameter,&block)
    Sequence.new(list,*parameter,&block)
  end

  def Real(s,e)
    case e
    when Sequence
      Real.new(s,e)
    when "CF"
      seq=s.ContFraction_to_ApproxFractions
      Real.new(seq,seq.delta.abs)
    end
  end
end

class Numeric
  def to_ContFraction
    a=numerator
    b=denominator
    s=Sequence([a,b]){"|n| self[n-2]%self[n-1] rescue nil "}
    Sequence([],s){"|n,s| s[n].div(s[n+1]) rescue nil"}
  end

  def to_Real
    Real(to_ContFraction,"CF")
  end
end

class Rational
  def floor
    numerator.div(denominator)
  end

  def to_f
    return -(-self).to_f if self<0
    return floor.to_f+(self-floor).to_f if self>1
    return 1/(1/self).to_f if denominator.to_f.infinite?
    numerator.to_f/denominator
  end
end

----