# Rationalization of floating point numbers. #-- # Copyright (C) 2003-2005, Javier Goizueta # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. #++ # # Author:: Javier Goizueta (mailto:javier@goizueta.info) # Copyright:: Copyright (c) 2002-2004 Javier Goizueta & Joe Horn # License:: Distributes under the GPL license # # This file provides conversion from floating point numbers # to rational numbers. # Algorithms by Joe Horn are used. # # The rational approximation algorithms are implemented in the class Nio::Rtnlzr # and there's an interface to the chosen algorithms through: # * Float#nio_r # * BigDecimal#nio_r # There's also exact rationalization implemented in: # * Float#nio_xr # * BigDecimal#nio_r require 'nio/tools' require 'nio/flttol' require 'rational' require 'bigdecimal' class Float # Conversion to Rational preserving the exact value of the number. def nio_xr return Rational(self.to_i,1) if self.modulo(1)==0 if !self.finite? return Rational(0,0) if self.nan? return self<0 ? Rational(-1,0) : Rational(1,0) end f,e = Math.frexp(self) if e < Float::MIN_EXP bits = e+Float::MANT_DIG-Float::MIN_EXP else bits = [Float::MANT_DIG,e].max #return Rational(self.to_i,1) if bits0 p,q = 1,0 (1..a.size).each{|i| p,q=q+p*a[-i],p} num,den = q,p num = -num if negans end return num,den end # This is algorithm PDQ2 by Joe Horn. def rationalize_Horn(x) num_tol = @tol.kind_of?(Numeric) if !num_tol && @tol.zero?(x) # num,den = x.nio_xr.nio_num_den num,den = 0,1 else negans=false if x<0 negans = true x = -x end dx = num_tol ? @tol : @tol.get_value(x) z,t = x,dx # renaming a,b = t.nio_xr.nio_num_den n0,d0 = (n,d = z.nio_xr.nio_num_den) 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 num,den = x,y # renaming num = -num if negans end return num,den end # This is from a RPL program by Tony Hutchins (PDR6). def rationalize_HornHutchins(x) num_tol = @tol.kind_of?(Numeric) if !num_tol && @tol.zero?(x) # num,den = x.nio_xr.nio_num_den num,den = 0,1 else negans=false if x<0 negans = true x = -x end dx = num_tol ? @tol : @tol.get_value(x) z,t = x,dx # renaming a,b = t.nio_xr.nio_num_den n0,d0 = (n,d = z.nio_xr.nio_num_den) 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 num,den = x,y # renaming num = -num if negans end return num,den 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 Rtnlzr.max_denominator(f, max_den=1000000000, num_class=nil) return nil if max_den<1 num_class ||= f.class return mth.ip(f),1 if mth.fp(f)==0 one = 1.prec(num_class) sign = f<0 f = -f if sign a,b,c = 0,1,f while bmax_den b -= a*mth.ceil((b-max_den)/Float(a)) end f1,f2 = [a,b].collect{|x| mth.abs(mth.rnd(x*f)/x.prec(num_class)-f)} a = f1>f2 ? b : a num,den = mth.rnd(a*f).to_i,a den = 1 if mth.abs(den)<1 num = -num if sign return num,den end class Rtnlzr private #Auxiliary floating-point functions module Mth # :nodoc: def self.fp(x) # y =x.modulo(1); return x<0 ? -y : y; x-ip(x) end def self.ip(x) # x.to_i.to_f (x<0 ? x.ceil : x.floor).to_i end def self.rnd(x) #x.round.to_i x.round end def self.abs(x) x.abs end def self.ceil(x) x.ceil.to_i end end def self.mth; Mth; end end module_function end