module Flt module Support # This class provides efficient conversion of fraction (as approximate floating point numbers) # to rational numbers. class Rationalizer # Exact conversion to rational. Ruby provides this method for all numeric types # since version 1.9.1, but before that it wasn't available for Float or BigDecimal. # This methods supports old Ruby versions. def self.to_r(x) if x.respond_to?(:to_r) x.to_r else case x when Float # Float did not had a #to_r method until Ruby 1.9.1 return Rational(x.to_i, 1) if x.modulo(1) == 0 if !x.finite? return Rational(0, 0) if x.nan? return x < 0 ? Rational(-1, 0) : Rational(1, 0) end f, e = Math.frexp(x) if e < Float::MIN_EXP bits = e + Float::MANT_DIG - Float::MIN_EXP else bits = [Float::MANT_DIG,e].max # return Rational(x.to_i, 1) if bits < e end p = Math.ldexp(f, bits) e = bits - e if e < Float::MAX_EXP q = Math.ldexp(1, e) else q = Float::RADIX**e end Rational(p.to_i, q.to_i) when BigDecimal # BigDecimal probably didn't have #to_r at some point s, f, b, e = x.split p = f.to_i p = -p if s < 0 e = f.size - e if e < 0 p *= b**(-e) e = 0 end q = b**(e) Rational(p,q) else x.to_r end end end # Convenience methods module AuxiliarFunctions private def num_den(x) x = to_r(x) [x.numerator, x.denominator] end # fraction part def fp(x) # y = x.modulo(1); return x<0 ? -y : y; x-ip(x) end # integer part def ip(x) # Note that ceil, floor return an Integer for Float and Flt::Num, but not for BigDecimal (x<0 ? x.ceil : x.floor).to_i end # round to integer def rnd(x) # Note that round returns an Integer for Float and Flt::Num, but not for BigDecimal x.round.to_i end # absolute value def abs(x) x.abs end def ceil(x) # Note that ceil returns an Integer for Float and Flt::Num, but not for BigDecimal x.ceil.to_i end def to_r(x) Rationalizer.to_r(x) end def special?(x) !x.finite? # x.class.context.special?(x) end def sign(x) x.class.context.sign(x) end end include AuxiliarFunctions extend AuxiliarFunctions # Create Rationalizator with given tolerance. def initialize(tol=Tolerance(:epsilon)) @tol = tol end def self.[](*args) new(*args) end # Rationalization method that finds the fraction with # smallest denominator fraction within the tolerance distance # of an approximate (floating point) number. # def rationalize(x) # Use the algorithm which has been found most efficient, rationalize_Knuth. rationalize_Knuth(x) end # This algorithm is derived from exercise 39 of 4.5.3 in # "The Art of Computer Programming", by Donald E. Knuth. def rationalize_Knuth(value) rationalization(value) do |x, dx| x = to_r(x) dx = to_r(dx) xp,xq = num_den(x-dx) yp,yq = num_den(x+dx) a = [] fin, odd = false, false while !fin && xp != 0 && yp != 0 odd = !odd xp,xq = xq,xp ax = xp.div(xq) xp -= ax*xq yp,yq = yq,yp ay = yp.div(yq) yp -= ay*yq if ax!=ay fin = true ax,xp,xq = ay,yp,yq if odd end a << ax # .to_i end a[-1] += 1 if xp != 0 && a.size > 0 p,q = 1,0 (1..a.size).each{|i| p, q = q+p*a[-i], p} [q, p] end end # This is algorithm PDQ2 by Joe Horn. def rationalize_Horn(x) rationalization(x) do |z, t| a,b = num_den(t) n0,d0 = (n,d = num_den(z)) cn,x,pn,cd,y,pd,lo,hi,mid,q,r = 1,1,0,0,0,1,0,1,1,0,0 begin q,r = n.divmod(d) x = q*cn+pn y = q*cd+pd pn = cn cn = x pd = cd cd = y n,d = d,r end until b*(n0*y-d0*x).abs <= a*d0*y if q > 1 hi = q begin mid = (lo + hi).div(2) x = cn - pn*mid y = cd - pd*mid if b*(n0*y - d0*x).abs <= a*d0*y lo = mid else hi = mid end end until hi - lo <= 1 x = cn - pn*lo y = cd - pd*lo end [x, y] end end # This is from a RPL program by Tony Hutchins (PDR6). def rationalize_HornHutchins(x) rationalization(x) do |z, t| a,b = num_den(t) n0,d0 = (n,d = num_den(z)) cn,x,pn,cd,y,pd,lo,hi,mid,q,r = 1,1,0,0,0,1,0,1,1,0,0 begin q,r = n.divmod(d) x = q*cn+pn y = q*cd+pd pn = cn cn = x pd = cd cd = y n,d = d,r end until b*(n0*y-d0*x).abs <= a*d0*y if q > 1 hi = q begin mid = (lo + hi).div(2) x = cn - pn*mid y = cd - pd*mid if b*(n0*y - d0*x).abs <= a*d0*y lo = mid else hi = mid end end until hi - lo <= 1 x = cn - pn*lo y = cd - pd*lo end [x, y] end end # Best fraction given maximum denominator # Algorithm Copyright (c) 1991 by Joseph K. Horn. # # The implementation of this method uses floating point # arithmetic which limits the magnitude and precision of the results, specially # using Float values. def self.max_denominator(f, max_den=1000000000, num_class=nil) return rationalize_special(f) if special?(f) return nil if max_den < 1 num_class ||= f.class context = num_class.context return ip(f),1 if fp(f) == 0 cast = lambda{|x| context.Num(x)} one = cast[1] sign = f < 0 f = -f if sign a,b,c = 0,1,f while b < max_den && c != 0 cc = one/c a,b,c = b, ip(cc)*b+a, fp(cc) end if b>max_den b -= a*ceil(cast[b-max_den]/a) end f1,f2 = [a,b].collect{|x| abs(cast[rnd(x*f)]/x-f)} a = f1 > f2 ? b : a num,den = rnd(a*f).to_i,a den = 1 if abs(den) < 1 num = -num if sign return num,den end def self.rationalize_special(x) if x.nan? [0, 0] else [sign(x), 0] end end private def rationalization(x) return Rationalizer.rationalize_special(x) if special?(x) num_tol = @tol.kind_of?(Numeric) if !num_tol && @tol.zero?(x) # num,den = num_den(x) num,den = 0,1 else negans = false if x<0 negans = true x = -x end dx = num_tol ? @tol : @tol.value(x) num, den = yield x, dx num = -num if negans end [num, den] end end end end