正木です。

[ruby-math:00789] Re: Float#to_rational で
Float の計算精度を信用して Float#error を訂正しましたが、
必ずしも信用できないことが分かりました:

1.0/n は n=2731,4623,5462,5607,5963,6147,9246,・・・
の場合に正しい(Float の中で最も近い)値を返しません。

これらの場合に、1/n の仮数部を64桁まで(と 1.0/n の仮数部を52
桁まで)調べてみると
1/2731
1.0111111111110100000000000101111111111101000000000001011111111111
1.0/2731
1.0111111111110100000000000101111111111101000000000010
1/4623
1.1100010110100010011010100110111001101011000110001000100000000000
1.0/4623
1.1100010110100010011010100110111001101011000110001000
のように 53 bit 目以降が
011111111111
あるいは
100000000000
のどちらかになっています。
これから想像すると FPU の設計ミスかなにかで(本当は切捨てにすべき)
64 bit 目を 0 捨 1 入して
100000000000
これを 64 bit Float(仮数部 52 bit) に変換する時に、52 bit 目に適当に
切り上げたり切捨てたりしているようです。
この bug のせいで Float の誤差は少し大きくなりますので、次のように再
訂正します。

class Float
  def error  # for intel cpu
    a,e=Math.frexp(self)
    1/2**(54-e)+1/2**(64-e) 
#   あるいは他にも bug がある可能性も考えて少し大きめに
#   1/2**(53-e) 
  end
end


ついでにこれはどの程度実用になるか分かりませんが:

次のように再定義すると(再定義の副作用がありそうな所は適当に書き直して):

class Float
  def +(other)
    (to_rational + other.to_rational).to_f
  end

  def -(other)
    (to_rational - other.to_rational).to_f
  end

  def *(other)
    (to_rational * other.to_rational).to_f
  end
end
class Numeric
  def to_rational
    self
  end
end

計算速度は犠牲になりますが、次のようにほぼ普通に期待されるとおりの
結果になります。

2**n+0.1-2**n==0.1 #=> true (n<48)
(1.0/n)*n==1 #=> true
(確かめたのは(n<=100000 の範囲ですがかなり大きな数まで大丈夫な筈)