lib/nio/repdec.rb in nio-0.2.1 vs lib/nio/repdec.rb in nio-0.2.2

- old
+ new

@@ -1,507 +1,507 @@ -# repdec.rb -- Repeating Decimals (Repeating Numerals, actually) - -# Copyright (C) 2003-2005, Javier Goizueta <javier@goizueta.info> -# -# 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. - -require 'nio/tools' -module Nio - - class RepDecError <StandardError - end - - class DigitsDef - include StateEquivalent - def initialize(ds='0123456789', cs=true) - @digits = ds - @casesens = cs - @dncase = (ds.downcase==ds) - @radix = @digits.size - end - def is_digit?(ch_code) - ch_code = set_case(ch_code) unless @casesens - @digits.include?(ch_code) - end - def digit_value(ch_code) - ch_code = set_case(ch_code) unless @casesens - @digits.index(ch_code) - end - def digit_char(v) - @digits[v] - end - def digit_char_safe(v) - v>=0 && v<@radix ? @digits[v] : nil - end - def radix - @radix - end - def DigitsDef.base(b,dncase=false,casesens=false) - dgs = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[0,b] - dgs.downcase! if dncase - DigitsDef.new(dgs,casesens) - end - private - def set_case(ch_code) - ch_code = ch_code.chr if ch_code.kind_of?(Numeric) - @dncase ? ch_code.downcase[0] : ch_code.upcase[0] - end - end - - - # RepDec handles repeating decimals (repeating numerals actually) - class RepDec - include StateEquivalent - - class Opt # :nodoc: - include StateEquivalent - def initialize() #default options - - @begin_rep = '<' - @end_rep = '>' - - @auto_rep = '...' - - @dec_sep = '.' - @grp_sep = ',' - @grp = [] # [3] for thousands separators - - @inf_txt = 'Infinity' - @nan_txt = 'NaN' - - @digits = DigitsDef.new - @digits_defined = false - - @max_d = 5000 - - end - attr_accessor :begin_rep, :end_rep, :auto_rep, :dec_sep, :grp_sep, :grp, :max_d - attr_accessor :nan_txt, :inf_txt - - def set_delim(begin_d,end_d='') - @begin_rep = begin_d - @end_rep = end_d - return self - end - def set_suffix(a) - @auto_rep = a - return self - end - def set_sep(d) - @dec_sep = a - return self - end - def set_grouping(sep,g=[]) - @grp_sep = a - @grp = g - return self - end - def set_special(nan_txt, inf_txt) - @nan_txt = nan_txt - @inf_txt = inf_txt - return self - end - - def set_digits(ds, dncase=false, casesens=false) - if ds - @digits_defined = true - if ds.kind_of?(DigitsDef) - @digits = ds - elsif ds.kind_of?(Numeric) - @digits = DigitsDef.base(ds, dncase, casesens) - else - @digits = DigitsDef.new(ds,casesens) - end - else - @digits = DigitsDef.new - @digits_defined = false - end - self - end - - attr_accessor :digits - def digits_defined? - @digits_defined - end - - end - - DEF_OPT=Opt.new - - - def initialize(b=10) - setZ(b) - end - - def setZ(b=10) - @ip = 0; - @d = []; - @rep_i = nil; - @sign = 0; - @radix = b; - self - end - - def setS(str, opt=DEF_OPT) - setZ(opt.digits_defined? ? opt.digits.radix : @radix); - sgn,i_str,f_str,ri,detect_rep = RepDec.parse(str,opt) - if i_str.kind_of?(Symbol) - @ip = i_str - else - @ip = i_str.to_i(@radix); # this assumes conventional digits - end - @sign = sgn - @rep_i = ri if ri - f_str.each_byte{|b| @d.push opt.digits.digit_value(b)} unless f_str.nil? - - if detect_rep then - - for l in 1..(@d.length/2) - l = @d.length/2 + 1 - l; - if @d[-l..-1]==@d[-2*l...-l] - - for m in 1..l - if l.modulo(m)==0 then - reduce_l = true; - for i in 2..l/m - if @d[-m..-1]!=@d[-i*m...-i*m+m] then - reduce_l = false; - break; - end - end - if reduce_l then - l = m - break - end - end - end - - - @rep_i = @d.length - 2*l; - l.times { @d.pop } - - - while @d.length >= 2*l && @d[-l..-1]==@d[-2*l...-l] - - @rep_i = @d.length - 2*l; - l.times { @d.pop } - - end - - break - end - end - - end - - - if @rep_i!=nil then - if @d.length==@rep_i+1 && @d[@rep_i]==0 then - @rep_i = nil; - @d.pop; - end - end - @d.pop while @d[@d.length-1]==0 - - self - end - - def RepDec.parse(str, opt=DEF_OPT) - sgn,i_str,f_str,ri,detect_rep = nil,nil,nil,nil,nil - - i = 0; - l = str.length; - - detect_rep = false; - - - i += 1 while i<str.length && str[i,1] =~/\s/ - - - neg = false; - - neg = true if str[i,1]=='-' - i += 1 if str[i,1]=='-' || str[i,1]=='+' - - - i += 1 while i<str.length && str[i,1] =~/\s/ - - - str.upcase! - if str[i,opt.nan_txt.size]==opt.nan_txt.upcase - i_str = :indeterminate; - elsif str[i,opt.inf_txt.size]==opt.inf_txt.upcase - i_str = neg ? :neginfinity : :posinfinity; - end - - unless i_str - i_str = "0"; - while i<l && str[i,1]!=opt.dec_sep - break if str[i,opt.auto_rep.length]==opt.auto_rep && opt.auto_rep!='' - i_str += str[i,1] if str[i,1]!=opt.grp_sep - i += 1; - end - sgn = neg ? -1 : +1 - i += 1; # skip the decimal separator - end - - unless i_str.kind_of?(Symbol) - j = 0; - f_str = '' - while i<l - ch = str[i,1]; - if ch==opt.begin_rep then - ri = j; - elsif ch==opt.end_rep then - i = l; - elsif ch==opt.auto_rep[0,1] then - detect_rep = true; - i = l; - else - f_str << ch - j += 1; - end - i += 1; - end - end - return [sgn,i_str,f_str,ri,detect_rep] - end - - def getS(nrep=0, opt=DEF_OPT) - raise RepDecError,"Base mismatch: #{opt.digits.radix} when #{@radix} was expected." if opt.digits_defined? && @radix!=opt.digits.radix - - if !ip.is_a?(Integer) then - str=opt.nan_txt if ip==:indeterminate; - str=opt.inf_txt if ip==:posinfinity - str='-'+opt.inf_txt if ip==:neginfinity - return str; - end - - s = ""; - s += '-' if @sign<0 - s += RepDec.group_digits(@ip.to_s(@radix),opt); - s += opt.dec_sep if @d.length>0; - for i in 0...@d.length - break if nrep>0 && @rep_i==i; - s += opt.begin_rep if i==@rep_i; - s << opt.digits.digit_char(@d[i]) - end; - if nrep>0 then - if @rep_i!=nil then - nrep += 1; - nrep.times do - for i in @rep_i...@d.length - s << opt.digits.digit_char(@d[i]) - end - end - - check = RepDec.new; - check.setS s+opt.auto_rep, opt; - #print " s=",s,"\n" - #print " self=",self.to_s,"\n" - while check!=self - for i in @rep_i...@d.length - s << opt.digits.digit_char(@d[i]) - end - check.setS s+opt.auto_rep, opt; - end - - s += opt.auto_rep; - end - else - s += opt.end_rep if @rep_i!=nil; - end - return s; - end - - def to_s() - getS - end - - def normalize!(remove_trailing_zeros=true) - if ip.is_a?(Integer) - if @rep_i!=nil && @rep_i==@d.length-1 && @d[@rep_i]==(@radix-1) then - @d.pop; - @rep_i = nil; - - i = @d.length-1; - carry = 1; - while carry>0 && i>=0 - @d[i] += carry; - carry = 0; - if @d[i]>(@radix) then - carry = 1; - @d[i]=0; - @d.pop if i==@d.length; - end - i -= 1; - end - @ip += carry; - - end - - if @rep_i!=nil && @rep_i>=@d.length - @rep_i = nil - end - - if @rep_i!=nil && @rep_i>=0 - unless @d[@rep_i..-1].find {|x| x!=0} - @d = @d[0...@rep_i] - @rep_i = nil - end - end - if @rep_i==nil && remove_trailing_zeros - while @d[@d.length-1]==0 - @d.pop - end - end - - end - end - - def copy() - c = clone - c.d = d.clone - return c; - end - - def ==(c) - a = copy; - b = c.copy; - a.normalize! - b.normalize! - return a.ip==b.ip && a.d==b.d && a.rep_i==b.rep_i - end - - #def !=(c) - # return !(self==c); - #end - - # Change the maximum number of digits that RepDec objects - # can handle. - def RepDec.maximum_number_of_digits=(n) - @max_d = [n,2048].max - end - # Return the maximum number of digits that RepDec objects - # can handle. - def RepDec.maximum_number_of_digits - @max_d - end - - def setQ(x,y, opt=DEF_OPT) - @radix = opt.digits.radix if opt.digits_defined? - xy_sign = x==0 ? 0 : x<0 ? -1 : +1; - xy_sign = -xy_sign if y<0; - @sign = xy_sign - x = x.abs; - y = y.abs; - - @d = []; - @rep_i = nil; - - if y==0 then - if x==0 then - @ip = :indeterminate - else - @ip = xy_sign==-1 ? :neginfinity : :posinfinity - end - return self - end - - k = {}; - @ip = x.div(y) #x/y; - x -= @ip*y; - i = 0; - ended = false; - - max_d = opt.max_d - while x>0 && @rep_i==nil && (max_d<=0 || i<max_d) - @rep_i = k[x] - if @rep_i.nil? then - k[x] = i; - x *= @radix - d,x = x.divmod(y) - @d.push d - i += 1; - end - end - self - end - - def getQ(opt=DEF_OPT) - raise RepDecError,"Base mismatch: #{opt.digits.radix} when #{@radix} was expected." if opt.digits_defined? && @radix!=opt.digits.radix - - if !ip.is_a?(Integer) then - y = 0; - x=0 if ip==:indeterminate; - x=1 if ip==:posinfinity - x=-1 if ip==:neginfinity - return x,y; - end if - - - n = @d.length - a = @ip - b = a - for i in 0...n - a*=@radix - a+=@d[i]; - if @rep_i!=nil && i<@rep_i - b *= @radix - b += @d[i]; - end - end - - x = a - x -= b if @rep_i!=nil - - y = @radix**n - y -= @radix**@rep_i if @rep_i!=nil - - d = Nio.gcd(x,y) - x /= d - y /= d - - x = -x if @sign<0 - - return x,y; - end - - #protected - - attr_reader :d, :ip, :rep_i, :sign; - attr_writer :d, :ip, :rep_i, :sign; - - end - - - def RepDec.group_digits(digits, opt) - if opt.grp_sep!=nil && opt.grp_sep!='' && opt.grp.length>0 - grouped = '' - i = 0 - while digits.length>0 - l = opt.grp[i] - l = digits.length if l>digits.length - grouped = opt.grp_sep + grouped if grouped.length>0 - grouped = digits[-l,l] + grouped - digits = digits[0,digits.length-l] - i += 1 if i<opt.grp.length-1 - end - grouped - else - digits - end - end - - module_function - - def gcd(a,b) - while b!=0 do - a,b = b, a.modulo(b) - end - return a.abs; - end - -end +# repdec.rb -- Repeating Decimals (Repeating Numerals, actually) + +# Copyright (C) 2003-2005, Javier Goizueta <javier@goizueta.info> +# +# 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. + +require 'nio/tools' +module Nio + + class RepDecError <StandardError + end + + class DigitsDef + include StateEquivalent + def initialize(ds='0123456789', cs=true) + @digits = ds + @casesens = cs + @dncase = (ds.downcase==ds) + @radix = @digits.size + end + def is_digit?(ch_code) + ch_code = set_case(ch_code) unless @casesens + @digits.include?(ch_code) + end + def digit_value(ch_code) + ch_code = set_case(ch_code) unless @casesens + @digits.index(ch_code.chr) + end + def digit_char(v) + @digits[v] + end + def digit_char_safe(v) + v>=0 && v<@radix ? @digits[v] : nil + end + def radix + @radix + end + def DigitsDef.base(b,dncase=false,casesens=false) + dgs = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[0,b] + dgs.downcase! if dncase + DigitsDef.new(dgs,casesens) + end + private + def set_case(ch_code) + ch_code = ch_code.chr if ch_code.kind_of?(Numeric) + @dncase ? ch_code.downcase[0] : ch_code.upcase[0] + end + end + + + # RepDec handles repeating decimals (repeating numerals actually) + class RepDec + include StateEquivalent + + class Opt # :nodoc: + include StateEquivalent + def initialize() #default options + + @begin_rep = '<' + @end_rep = '>' + + @auto_rep = '...' + + @dec_sep = '.' + @grp_sep = ',' + @grp = [] # [3] for thousands separators + + @inf_txt = 'Infinity' + @nan_txt = 'NaN' + + @digits = DigitsDef.new + @digits_defined = false + + @max_d = 5000 + + end + attr_accessor :begin_rep, :end_rep, :auto_rep, :dec_sep, :grp_sep, :grp, :max_d + attr_accessor :nan_txt, :inf_txt + + def set_delim(begin_d,end_d='') + @begin_rep = begin_d + @end_rep = end_d + return self + end + def set_suffix(a) + @auto_rep = a + return self + end + def set_sep(d) + @dec_sep = a + return self + end + def set_grouping(sep,g=[]) + @grp_sep = a + @grp = g + return self + end + def set_special(nan_txt, inf_txt) + @nan_txt = nan_txt + @inf_txt = inf_txt + return self + end + + def set_digits(ds, dncase=false, casesens=false) + if ds + @digits_defined = true + if ds.kind_of?(DigitsDef) + @digits = ds + elsif ds.kind_of?(Numeric) + @digits = DigitsDef.base(ds, dncase, casesens) + else + @digits = DigitsDef.new(ds,casesens) + end + else + @digits = DigitsDef.new + @digits_defined = false + end + self + end + + attr_accessor :digits + def digits_defined? + @digits_defined + end + + end + + DEF_OPT=Opt.new + + + def initialize(b=10) + setZ(b) + end + + def setZ(b=10) + @ip = 0; + @d = []; + @rep_i = nil; + @sign = 0; + @radix = b; + self + end + + def setS(str, opt=DEF_OPT) + setZ(opt.digits_defined? ? opt.digits.radix : @radix); + sgn,i_str,f_str,ri,detect_rep = RepDec.parse(str,opt) + if i_str.kind_of?(Symbol) + @ip = i_str + else + @ip = i_str.to_i(@radix); # this assumes conventional digits + end + @sign = sgn + @rep_i = ri if ri + f_str.each_byte{|b| @d.push opt.digits.digit_value(b)} unless f_str.nil? + + if detect_rep then + + for l in 1..(@d.length/2) + l = @d.length/2 + 1 - l; + if @d[-l..-1]==@d[-2*l...-l] + + for m in 1..l + if l.modulo(m)==0 then + reduce_l = true; + for i in 2..l/m + if @d[-m..-1]!=@d[-i*m...-i*m+m] then + reduce_l = false; + break; + end + end + if reduce_l then + l = m + break + end + end + end + + + @rep_i = @d.length - 2*l; + l.times { @d.pop } + + + while @d.length >= 2*l && @d[-l..-1]==@d[-2*l...-l] + + @rep_i = @d.length - 2*l; + l.times { @d.pop } + + end + + break + end + end + + end + + + if @rep_i!=nil then + if @d.length==@rep_i+1 && @d[@rep_i]==0 then + @rep_i = nil; + @d.pop; + end + end + @d.pop while @d[@d.length-1]==0 + + self + end + + def RepDec.parse(str, opt=DEF_OPT) + sgn,i_str,f_str,ri,detect_rep = nil,nil,nil,nil,nil + + i = 0; + l = str.length; + + detect_rep = false; + + + i += 1 while i<str.length && str[i,1] =~/\s/ + + + neg = false; + + neg = true if str[i,1]=='-' + i += 1 if str[i,1]=='-' || str[i,1]=='+' + + + i += 1 while i<str.length && str[i,1] =~/\s/ + + + str.upcase! + if str[i,opt.nan_txt.size]==opt.nan_txt.upcase + i_str = :indeterminate; + elsif str[i,opt.inf_txt.size]==opt.inf_txt.upcase + i_str = neg ? :neginfinity : :posinfinity; + end + + unless i_str + i_str = "0"; + while i<l && str[i,1]!=opt.dec_sep + break if str[i,opt.auto_rep.length]==opt.auto_rep && opt.auto_rep!='' + i_str += str[i,1] if str[i,1]!=opt.grp_sep + i += 1; + end + sgn = neg ? -1 : +1 + i += 1; # skip the decimal separator + end + + unless i_str.kind_of?(Symbol) + j = 0; + f_str = '' + while i<l + ch = str[i,1]; + if ch==opt.begin_rep then + ri = j; + elsif ch==opt.end_rep then + i = l; + elsif ch==opt.auto_rep[0,1] then + detect_rep = true; + i = l; + else + f_str << ch + j += 1; + end + i += 1; + end + end + return [sgn,i_str,f_str,ri,detect_rep] + end + + def getS(nrep=0, opt=DEF_OPT) + raise RepDecError,"Base mismatch: #{opt.digits.radix} when #{@radix} was expected." if opt.digits_defined? && @radix!=opt.digits.radix + + if !ip.is_a?(Integer) then + str=opt.nan_txt if ip==:indeterminate; + str=opt.inf_txt if ip==:posinfinity + str='-'+opt.inf_txt if ip==:neginfinity + return str; + end + + s = ""; + s += '-' if @sign<0 + s += RepDec.group_digits(@ip.to_s(@radix),opt); + s += opt.dec_sep if @d.length>0; + for i in 0...@d.length + break if nrep>0 && @rep_i==i; + s += opt.begin_rep if i==@rep_i; + s << opt.digits.digit_char(@d[i]) + end; + if nrep>0 then + if @rep_i!=nil then + nrep += 1; + nrep.times do + for i in @rep_i...@d.length + s << opt.digits.digit_char(@d[i]) + end + end + + check = RepDec.new; + check.setS s+opt.auto_rep, opt; + #print " s=",s,"\n" + #print " self=",self.to_s,"\n" + while check!=self + for i in @rep_i...@d.length + s << opt.digits.digit_char(@d[i]) + end + check.setS s+opt.auto_rep, opt; + end + + s += opt.auto_rep; + end + else + s += opt.end_rep if @rep_i!=nil; + end + return s; + end + + def to_s() + getS + end + + def normalize!(remove_trailing_zeros=true) + if ip.is_a?(Integer) + if @rep_i!=nil && @rep_i==@d.length-1 && @d[@rep_i]==(@radix-1) then + @d.pop; + @rep_i = nil; + + i = @d.length-1; + carry = 1; + while carry>0 && i>=0 + @d[i] += carry; + carry = 0; + if @d[i]>(@radix) then + carry = 1; + @d[i]=0; + @d.pop if i==@d.length; + end + i -= 1; + end + @ip += carry; + + end + + if @rep_i!=nil && @rep_i>=@d.length + @rep_i = nil + end + + if @rep_i!=nil && @rep_i>=0 + unless @d[@rep_i..-1].find {|x| x!=0} + @d = @d[0...@rep_i] + @rep_i = nil + end + end + if @rep_i==nil && remove_trailing_zeros + while @d[@d.length-1]==0 + @d.pop + end + end + + end + end + + def copy() + c = clone + c.d = d.clone + return c; + end + + def ==(c) + a = copy; + b = c.copy; + a.normalize! + b.normalize! + return a.ip==b.ip && a.d==b.d && a.rep_i==b.rep_i + end + + #def !=(c) + # return !(self==c); + #end + + # Change the maximum number of digits that RepDec objects + # can handle. + def RepDec.maximum_number_of_digits=(n) + @max_d = [n,2048].max + end + # Return the maximum number of digits that RepDec objects + # can handle. + def RepDec.maximum_number_of_digits + @max_d + end + + def setQ(x,y, opt=DEF_OPT) + @radix = opt.digits.radix if opt.digits_defined? + xy_sign = x==0 ? 0 : x<0 ? -1 : +1; + xy_sign = -xy_sign if y<0; + @sign = xy_sign + x = x.abs; + y = y.abs; + + @d = []; + @rep_i = nil; + + if y==0 then + if x==0 then + @ip = :indeterminate + else + @ip = xy_sign==-1 ? :neginfinity : :posinfinity + end + return self + end + + k = {}; + @ip = x.div(y) #x/y; + x -= @ip*y; + i = 0; + ended = false; + + max_d = opt.max_d + while x>0 && @rep_i==nil && (max_d<=0 || i<max_d) + @rep_i = k[x] + if @rep_i.nil? then + k[x] = i; + x *= @radix + d,x = x.divmod(y) + @d.push d + i += 1; + end + end + self + end + + def getQ(opt=DEF_OPT) + raise RepDecError,"Base mismatch: #{opt.digits.radix} when #{@radix} was expected." if opt.digits_defined? && @radix!=opt.digits.radix + + if !ip.is_a?(Integer) then + y = 0; + x=0 if ip==:indeterminate; + x=1 if ip==:posinfinity + x=-1 if ip==:neginfinity + return x,y; + end if + + + n = @d.length + a = @ip + b = a + for i in 0...n + a*=@radix + a+=@d[i]; + if @rep_i!=nil && i<@rep_i + b *= @radix + b += @d[i]; + end + end + + x = a + x -= b if @rep_i!=nil + + y = @radix**n + y -= @radix**@rep_i if @rep_i!=nil + + d = Nio.gcd(x,y) + x /= d + y /= d + + x = -x if @sign<0 + + return x,y; + end + + #protected + + attr_reader :d, :ip, :rep_i, :sign; + attr_writer :d, :ip, :rep_i, :sign; + + end + + + def RepDec.group_digits(digits, opt) + if opt.grp_sep!=nil && opt.grp_sep!='' && opt.grp.length>0 + grouped = '' + i = 0 + while digits.length>0 + l = opt.grp[i] + l = digits.length if l>digits.length + grouped = opt.grp_sep + grouped if grouped.length>0 + grouped = digits[-l,l] + grouped + digits = digits[0,digits.length-l] + i += 1 if i<opt.grp.length-1 + end + grouped + else + digits + end + end + + module_function + + def gcd(a,b) + while b!=0 do + a,b = b, a.modulo(b) + end + return a.abs; + end + +end