lib/nio/fmt.rb in nio-0.2.3 vs lib/nio/fmt.rb in nio-0.2.4

- old
+ new

@@ -16,14 +16,16 @@ require 'rational' require 'bigdecimal' +require 'flt' + module Nio # positional notation, unformatted numeric literal: used as intermediate form - class NeutralNum + class NeutralNum include StateEquivalent def initialize(s='',d='',p=nil,r=nil,dgs=DigitsDef.base(10), inexact=false, round=:inf) set s,d,p,r,dgs,dgs, inexact, round end attr_reader :sign, :digits, :dec_pos, :rep_pos, :special, :inexact, :rounding @@ -38,11 +40,11 @@ @dgs = dgs @base = @dgs.radix @inexact = inexact @special = nil @rounding = rounding - trimZeros unless inexact + trimZeros unless inexact self end # set infinite (:inf) and invalid (:nan) numbers def set_special(s,sgn='') # :inf, :nan @special = s @@ -82,11 +84,11 @@ else #n.set @sign.dup, @digits.dup, @dec_pos.dup, @rep_pos.dup, @dgs.dup # in Ruby 1.6.8 Float,BigNum,Fixnum doesn't respond to dup n.set @sign.dup, @digits.dup, @dec_pos, @rep_pos, @dgs.dup, @inexact, @rounding end - return n + return n end def zero? z = false if !special @@ -117,28 +119,33 @@ n = [n,@digits.size].min if @inexact adj = 0 dv = :tie if @inexact && n==@digits.size - dv = @inexact==:roundup ? :hi : :lo + # TODO: the combination of the value true with the values of Formatter#round_up makes this ugly + dv = @inexact.is_a?(Symbol) ? @inexact : :lo else v = dig_value(n) v2 = 2*v if v2 < @base # v<((@base+1)/2) dv = :lo elsif v2 > @base # v>(@base/2) dv = :hi else - - (n+1...@digits.length).each do |i| - if dig_value(i)>0 - dv = :hi - break - end - end - - dv = :hi if dv==:tie && @rep_pos<=n + if @inexact + dv = :hi + else + + (n+1...@digits.length).each do |i| + if dig_value(i)>0 + dv = :hi + break + end + end + + end + dv = :hi if dv==:tie && @rep_pos<=n end end if dv==:hi adj = +1 @@ -147,14 +154,14 @@ adj = +1 elsif dir==:even # to nearest even digit (IEEE unbiased rounding) adj = +1 if (dig_value(n-1)%2)!=0 elsif dir==:zero # towards zero adj=0 - # elsif dir==:odd + # elsif dir==:odd # adj = +1 unless (dig_value(n-1)%2)!=0 end - end + end if n>@digits.length (@digits.length...n).each do |i| @digits << dig_char(dig_value(i)) @rep_pos += 1 @@ -173,21 +180,21 @@ elsif v>=@base v -= @base adj = +1 end if i<0 - prefix = dig_char(v)+prefix + prefix = dig_char(v)+prefix elsif i<@digits.length @digits[i] = dig_char(v) end i += -1 - end + end if n<0 - @digits = "" + @digits = "" else - @digits = @digits[0...n] + @digits = @digits[0...n] end @rep_pos = @digits.length if prefix!='' @digits = prefix + @digits @@ -203,14 +210,14 @@ nn = dup nn.round!(n,mode,dir) return nn end - def trimTrailZeros() + def trimTrailZeros() i = @digits.length while i>0 && dig_value(i-1)==0 - i -= 1 + i -= 1 end if @rep_pos>=i @digits = @digits[0...i] @rep_pos = i end @@ -238,24 +245,24 @@ @dec_pos = 1 end end - def trimZeros() + def trimZeros() trimLeadZeros trimTrailZeros end protected def dig_value(i) v = 0 - if i>=@rep_pos + if i>=@rep_pos i -= @digits.length i %= @digits.length - @rep_pos if @rep_pos<@digits.length i += @rep_pos - end + end if i>=0 && i<@digits.length v = @dgs.digit_value(@digits[i]) #digcode_value(@digits[i]) end return v>=0 && v<@base ? v : nil end @@ -300,12 +307,12 @@ else if dec_pos<=0 n.ip = 0 n.d = text_to_digits(dig_char(0)*(-dec_pos) + digits) elsif dec_pos >= digits.length - n.ip = digits.to_i(@base) - if rep_pos<dec_pos + n.ip = digits.to_i(@base) + if rep_pos<dec_pos i=0 (dec_pos-digits.length).times do n.ip *= @base n.ip += @dgs.digit_value(digits[rep_pos+i]) if rep_pos+i<digits.length i += 1 @@ -324,26 +331,26 @@ n.d = [] end else n.ip = digits[0...dec_pos].to_i(@base) n.d = text_to_digits(digits[dec_pos..-1]) - if rep_pos<dec_pos + if rep_pos<dec_pos new_rep_pos = n.d.size + dec_pos n.d += text_to_digits(digits[rep_pos..-1]) self.rep_pos = new_rep_pos puts "--rep_pos=#{rep_pos}" - end + end end n.sign = -1 if sign=='-' - n.rep_i = rep_pos - dec_pos + n.rep_i = rep_pos - dec_pos end n.normalize!(!inexact) # keep trailing zeros for inexact numbers return n end protected def text_to_digits(txt) - #txt.split('').collect{|c| @dgs.digit_value(c)} + #txt.split('').collect{|c| @dgs.digit_value(c)} ds = [] txt.each_byte{|b| ds << @dgs.digit_value(b)} ds end end @@ -362,11 +369,11 @@ when :neginfinity num.set_special :inf,'-' else num = nil end - + else base_dgs ||= DigitsDef.base(@radix) # assert base_dgs.radix == @radix signch = sign<0 ? '-' : '+' decimals = ip.to_s(@radix) @@ -374,11 +381,11 @@ d.each {|dig| decimals << base_dgs.digit_char(dig) } rep_pos = rep_i==nil ? decimals.length : dec_pos + rep_i num.set signch, decimals, dec_pos, rep_pos, base_dgs end return num - end + end end # A Fmt object defines a numeric format. # # The formatting aspects managed by Fmt are: @@ -390,11 +397,11 @@ # - see #sep() and #grouping() # * field justfification # - #width() and the shortcut #pad0s() # * numerical base # - #base() - # * repeating numerals + # * repeating numerals # - #rep() # # Note that for every aspect there are also corresponding _mutator_ # methos (its name ending with a bang) that modify an object in place, # instead of returning an altered copy. @@ -459,16 +466,16 @@ @base_prefix = false @nan_txt = 'NAN' @inf_txt = 'Infinity' - yield self if block_given? + yield self if block_given? end # Defines the separators used in numerals. This is relevant to - # both input and output. - # + # both input and output. + # # The first argument is the radix point separator (usually # a point or a comma; by default it is a point.) # # The second argument is the group separator. # @@ -493,16 +500,16 @@ # This is the mutator version of #grouping(). def grouping!(grp=[3],grp_sep=nil) set! :grp_sep=>grp_sep, :grp=>grp end - # This is a shortcut to return a new default Fmt object + # This is a shortcut to return a new default Fmt object # and define the separators as with #sep(). def Fmt.sep(dec_sep,grp_sep=nil,grp=nil) Fmt.default.sep(dec_sep,grp_sep,grp) end - # This is a shortcut to return a new default Fmt object + # This is a shortcut to return a new default Fmt object # and define the grouping as with #grouping(). def Fmt.grouping(grp=[3],grp_sep=nil) Fmt.default.grouping(grp,grp_sep) end @@ -524,14 +531,14 @@ # # Other paramters can be passed in a hash after <tt>precision</tt> # - <tt>:round</tt> rounding mode applied to conversions # (this is relevant for both input and output). It must be one of: # [<tt>:inf</tt>] - # rounds to nearest with ties toward infinite; + # rounds to nearest with ties toward infinite; # 1.5 is rounded to 2, -1.5 to -2 # [<tt>:zero</tt>] - # rounds to nearest with ties toward zero; + # rounds to nearest with ties toward zero; # 1.5 is rounded to 1, -1.5 to 2 # [<tt>:even</tt>] # rounds to the nearest with ties toward an even digit; # 1.5 rounds to 2, 2.5 to 2 # - <tt>:approx</tt> approximate mode @@ -542,11 +549,11 @@ # [<tt>:exact</tt>] # the value is interpreted as exact, there's no distinction between # significant and insignificant digits. # [<tt>:simplify</tt>] # the value is simplified, if possible to a simpler (rational) value. - # - <tt>:show_all_digits</tt> if true, this forces to show digits that + # - <tt>:show_all_digits</tt> if true, this forces to show digits that # would otherwise not be shown in the <tt>:gen</tt> format: trailing # zeros of exact types or non-signficative digits of inexact types. # - <tt>:nonsignficative_digits</tt> assigns a character to display # insignificant digits, # by default def mode(mode,precision=nil,options={}) @@ -560,22 +567,22 @@ # Defines the formatting mode like #mode() but using a different # order of the first two parameters parameters, which is useful # to change the precision only. Refer to #mode(). def prec(precision,mode=nil, options={}) dup.prec! precision, mode, options - end + end # This is the mutator version of #prec(). def prec!(precision,mode=:gen, options={}) set! options.merge(:mode=>mode, :ndig=>precision) end - # This is a shortcut to return a new default Fmt object + # This is a shortcut to return a new default Fmt object # and define the formatting mode as with #mode() def Fmt.mode(mode,ndig=nil,options={}) Fmt.default.mode(mode,ndig,options) end - # This is a shortcut to return a new default Fmt object + # This is a shortcut to return a new default Fmt object # and define the formatting mode as with #prec() def Fmt.prec(ndig,mode=nil,options={}) Fmt.default.prec(ndig,mode,options) end @@ -590,11 +597,11 @@ Fmt.default = Fmt.default.round(m) end # This controls the display of the digits that are not necessary # to specify the value unambiguosly (e.g. trailing zeros). - # + # # The true (default) value forces the display of the requested number of digits # and false will display only necessary digits. def show_all_digits(ad=true) dup.show_all_digits! ad end @@ -638,26 +645,26 @@ # This is the mutator version of #sci_digits(). def sci_digits!(n=-1) set! :sci_format=>n end - # This is a shortcut to return a new default Fmt object + # This is a shortcut to return a new default Fmt object # and define show_all_digits def Fmt.show_all_digits(v=true) Fmt.default.show_all_digits(v) end - # This is a shortcut to return a new default Fmt object + # This is a shortcut to return a new default Fmt object # and define approx_mode def Fmt.approx_mode(v) Fmt.default.approx_mode(v) end - # This is a shortcut to return a new default Fmt object + # This is a shortcut to return a new default Fmt object # and define insignificant digits def Fmt.insignificant_digits(v='#') Fmt.default.insignificant_digits(v) end - # This is a shortcut to return a new default Fmt object + # This is a shortcut to return a new default Fmt object # and define sci_digits def Fmt.sci_digits(v=-1) Fmt.default.sci_digits(v) end @@ -681,16 +688,16 @@ set! :show_exp_plus=>sp set! :plus_symbol=>sp if sp.kind_of?(String) self end - # This is a shortcut to return a new default Fmt object + # This is a shortcut to return a new default Fmt object # and define show_plus def Fmt.show_plus(v=true) Fmt.default.show_plus(v) end - # This is a shortcut to return a new default Fmt object + # This is a shortcut to return a new default Fmt object # and define show_exp_plus def Fmt.show_exp_plus(v=true) Fmt.default.show_exp_plus(v) end @@ -699,33 +706,33 @@ # [<tt>:begin</tt>] is the beginning delimiter of repeating section (<) # [<tt>:end</tt>] is the ending delimiter of repeating section (<) # [<tt>:suffix</tt>] is the suffix used to indicate a implicit repeating decimal # [<tt>:rep</tt>] # if this parameter is greater than zero, on output the repeating section - # is repeated the indicated number of times followed by the suffix; + # is repeated the indicated number of times followed by the suffix; # otherwise the delimited notation is used. # [<tt>:read</tt>] # (true/false) determines if repeating decimals are # recognized on input (true) def rep(*params) dup.rep!(*params) end # This is the mutator version of #rep(). - def rep!(*params) + def rep!(*params) params << {} if params.size==0 if params[0].kind_of?(Hash) params = params[0] - else - begch,endch,autoch,rep,read = *params + else + begch,endch,autoch,rep,read = *params params = {:begin=>begch,:end=>endch,:suffix=>autoch,:nreps=>rep,:read=>read} end set! params end - # This is a shortcut to return a new default Fmt object + # This is a shortcut to return a new default Fmt object # and define the repeating decimals mode as with #rep() def Fmt.rep(*params) Fmt.default.rep(*params) end @@ -820,21 +827,21 @@ @round end # Method used internally to format a neutral numeral def nio_write_formatted(neutral) # :nodoc: - str = '' + str = '' if neutral.special? str << neutral.sign case neutral.special when :inf str << @inf_txt when :nan str << @nan_txt - end + end else - zero = get_base_digits(neutral.base).digit_char(0).chr + zero = get_base_digits(neutral.base).digit_char(0).chr neutral = neutral.dup round! neutral if neutral.zero? str << neutral.sign if neutral.sign=='-' # show - if number was <0 before rounding str << zero @@ -859,20 +866,20 @@ else integral_digits = neutral.digits.length end end exp = neutral.dec_pos - integral_digits - + case actual_mode when :gen # general (automatic) # @ndig means significant digits actual_mode = :sig actual_mode = :sci if use_scientific?(neutral, exp) trim_trail_zeros = !@all_digits # true end - case actual_mode + case actual_mode when :fix, :sig #, :gen if @show_plus || neutral.sign!='+' str << ({'-'=>@minus_symbol, '+'=>@plus_symbol}[neutral.sign] || neutral.sign) @@ -901,11 +908,11 @@ end digits = neutral.digits + ns_digits if neutral.dec_pos<=0 str << zero+@dec_sep+zero*(-neutral.dec_pos) + digits - elsif neutral.dec_pos >= digits.length + elsif neutral.dec_pos >= digits.length str << group(digits + zero*(neutral.dec_pos-digits.length)) else str << group(digits[0...neutral.dec_pos]) + @dec_sep + digits[neutral.dec_pos..-1] end end @@ -915,11 +922,11 @@ str.chop! while str[-1]==zero[0] str.chomp!(@dec_sep) #puts str end - + when :sci if @show_plus || neutral.sign!='+' str << ({'-'=>@minus_symbol, '+'=>@plus_symbol}[neutral.sign] || neutral.sign) @@ -933,11 +940,11 @@ #zero = get_base_digits.digit_char(0).chr if @ndig==:exact neutral.sign = '+' neutral.dec_pos-=exp - str << neutral.to_RepDec.getS(@rep_n, getRepDecOpt(neutral.base)) + str << neutral.to_RepDec.getS(@rep_n, getRepDecOpt(neutral.base)) else ns_digits = '' nd = neutral.digits.length if actual_mode==:fix @@ -949,11 +956,11 @@ digits = neutral.digits + ns_digits str << ((integral_digits<1) ? zero : digits[0...integral_digits]) str << @dec_sep str << digits[integral_digits...@ndig] - pad_right =(@ndig+1-str.length) + pad_right =(@ndig+1-str.length) str << zero*pad_right if pad_right>0 && !neutral.inexact? # maybe we didn't have enought digits end #str = str.chomp(zero).chomp(@dec_sep) if trim_trail_zeros && str.include?(@dec_sep) if trim_trail_zeros && str.include?(@dec_sep) && str[-@rep_auto.size..-1]!=@rep_auto @@ -965,11 +972,11 @@ str << get_exp_char(neutral.base) if @show_exp_plus || exp<0 str << (exp<0 ? (@minus_symbol || '-') : (@plus_symbol || '+')) end str << exp.abs.to_s - + end end end @@ -983,11 +990,11 @@ l = @width - str.length if l>0 case @adjust when :internal sign = '' - if str[0,1]=='+' || str[0,1]=='-' + if str[0,1]=='+' || str[0,1]=='-' sign = str[0,1] str = str[1...str.length] end str = sign + @fill_char*l + str when :center @@ -995,11 +1002,11 @@ when :right str = @fill_char*l + str when :left str = str + @fill_char*l end - end + end end return str end @@ -1011,27 +1018,27 @@ @@sci_fmt = nil def nio_read_formatted(txt) # :nodoc: txt = txt.dup num = nil - + base = nil base ||= get_base - - zero = get_base_digits(base).digit_char(0).chr - txt.tr!(@non_sig,zero) # we don't simply remove it because it may be before the radix point - + + zero = get_base_digits(base).digit_char(0).chr + txt.tr!(@non_sig,zero) # we don't simply remove it because it may be before the radix point + exp = 0 x_char = get_exp_char(base) exp_i = txt.index(x_char) exp_i = txt.index(x_char.downcase) if exp_i===nil if exp_i!=nil exp = txt[exp_i+1...txt.length].to_i - txt = txt[0...exp_i] - end + txt = txt[0...exp_i] + end opt = getRepDecOpt(base) if @rep_in #raise InvalidFormat,"Invalid numerical base" if base!=10 @@ -1049,11 +1056,11 @@ num.rounding = get_round num.dec_pos += exp return num end - + @@fmts = { :def=>Fmt.new.freeze } # Returns the current default format. def self.default @@ -1074,11 +1081,11 @@ end # Retrieves a named format from the repository. def self.[](tag) @@fmts[tag.to_sym] end - + protected @@valid_properties = nil ALIAS_PROPERTIES = { :show_all_digits=>:all_digits, @@ -1095,30 +1102,30 @@ def set!(properties={}) # :nodoc: @@valid_properties ||= instance_variables.collect{|v| v[1..-1].to_sym} - + properties.each do |k,v| al = ALIAS_PROPERTIES[k] if al properties[al] = v properties.delete k elsif !@@valid_properties.include?(k) raise InvalidOption, "Invalid option: #{k}" end end - + - if properties[:grp_sep].nil? && !properties[:dec_sep].nil? && properties[:dec_sep]!=@dec_sep && properties[:dec_sep]==@grp_sep - properties[:grp_sep] = properties[:dec_sep]=='.' ? ',' : '.' + if properties[:grp_sep].nil? && !properties[:dec_sep].nil? && properties[:dec_sep]!=@dec_sep && properties[:dec_sep]==@grp_sep + properties[:grp_sep] = properties[:dec_sep]=='.' ? ',' : '.' end if properties[:all_digits].nil? && (properties[:ndig] || properties[:mode]) ndig = properties[:ndig] || @ndig mode = properties[:mode] || @mode - properties[:all_digits] = ndig!=:exact && mode!=:gen + properties[:all_digits] = ndig!=:exact && mode!=:gen end if !properties[:all_digits].nil? && properties[:non_sig].nil? properties[:non_sig] = '' unless properties[:all_digits] elsif !properties[:non_sig].nil? && properties[:all_digits].nil? @@ -1129,16 +1136,16 @@ base = properties[:base_radix] || @base_radix uppercase = properties[:base_uppercase] || @base_uppercase properties[:base_digits] = DigitsDef.base(base, !uppercase) end - + properties.each do |k,v| instance_variable_set "@#{k}", v unless v.nil? end - - self + + self end def set(properties={}) # :nodoc: self.dup.set!(properties) end @@ -1147,11 +1154,11 @@ nd = @ndig.kind_of?(Numeric) ? @ndig : [neutral.digits.length,10].max if @@sci_fmt==:hp puts " #{nd} ndpos=#{neutral.dec_pos} ndlen=#{neutral.digits.length}" neutral.dec_pos>nd || ([neutral.digits.length,nd].min-neutral.dec_pos)>nd else - exp<-4 || exp>=nd + exp<-4 || exp>=nd end end def getRepDecOpt(base=nil) # :nodoc: rd_opt = RepDec::Opt.new @@ -1160,11 +1167,11 @@ rd_opt.auto_rep = @rep_auto rd_opt.dec_sep = @dec_sep rd_opt.grp_sep = @grp_sep rd_opt.grp = @grp rd_opt.inf_txt = @inf_txt - rd_opt.nan_txt = @nan_txt + rd_opt.nan_txt = @nan_txt rd_opt.set_digits(get_base_digits(base)) # if base && (base != get_base_digits.radix) # rd_opt.set_digits(get_base_digits(base)) # else # rd_opt.set_digits get_base_digits @@ -1195,31 +1202,31 @@ fmt.nio_write_formatted(neutral) end module ClassMethods # This is the method available in all formattable clases - # to read a formatted value from a text string into + # to read a formatted value from a text string into # a value the class, according to the optional format passed. def nio_read(txt,fmt=Fmt.default) neutral = fmt.nio_read_formatted(txt) - nio_read_neutral neutral + nio_read_neutral neutral end end # Round a formattable object according to the rounding mode and # precision of a format. def nio_round(fmt=Fmt.default) neutral = nio_write_neutral(fmt) fmt.round! neutral - self.class.nio_read_neutral neutral + self.class.nio_read_neutral neutral end def self.append_features(mod) # :nodoc: super mod.extend ClassMethods end - + end Fmt[:comma] = Fmt.sep(',','.') Fmt[:comma_th] = Fmt.sep(',','.',[3]) Fmt[:dot] = Fmt.sep('.',',') @@ -1260,287 +1267,25 @@ module_function def nio_float_to_bigdecimal(x,prec) # :nodoc: if prec.nil? - x = Fmt.convert(x,BigDecimal,:approx) + x = Fmt.convert(x,BigDecimal,:approx) elsif prec==:exact - x = Fmt.convert(x,BigDecimal,:exact) + x = Fmt.convert(x,BigDecimal,:exact) else x = BigDecimal(x.nio_write(Nio::Fmt.new.prec(prec,:sig))) end x end - - module Clinger # :nodoc: all - module_function - - def algM(f,e,round_mode,eb=10,beta=Float::RADIX,n=Float::MANT_DIG,min_e=Float::MIN_EXP-Float::MANT_DIG,max_e=Float::MAX_EXP-Float::MANT_DIG) - - if e<0 - u,v,k = f,eb**(-e),0 - else - u,v,k = f*(eb**e),1,0 - end - - loop do - x = u.div(v) - # overflow if k>=max_e - if (x>=beta**(n-1) && x<beta**n) || k==min_e || k==max_e - return ratio_float(u,v,k,round_mode,beta,n) - elsif x<beta**(n-1) - u *= beta - k -= 1 - elsif x>=beta**n - v *= beta - k += 1 - end - end - - end - def ratio_float(u,v,k,round_mode,beta=Float::RADIX,n=Float::MANT_DIG) - q,r = u.divmod(v) - v_r = v-r - z = Math.ldexp(q,k) - if r<v_r - z - elsif r>v_r - nextfloat z - elsif (round_mode==:even && q.even?) || (round_mode==:zero) - z - else - nextfloat z - end - end - - # valid only for non-negative x - def nextfloat(x) - f,e = Math.frexp(x) - e = Float::MIN_EXP if f==0 - e = [Float::MIN_EXP,e].max - dx = Math.ldexp(1,e-Float::MANT_DIG) #Math.ldexp(Math.ldexp(1.0,-Float::MANT_DIG),e) - if f==(1.0 - Math.ldexp(1,-Float::MANT_DIG)) - x + dx*2 - else - x + dx - end - end - - # valid only for non-negative x - def prevfloat(x) - f,e = Math.frexp(x) - e = Float::MIN_EXP if f==0 - e = [Float::MIN_EXP,e].max - dx = Math.ldexp(1,e-Float::MANT_DIG) #Math.ldexp(Math.ldexp(1.0,-Float::MANT_DIG),e) - if e==Float::MIN_EXP || f!=0.5 #0.5==Math.ldexp(2**(bits-1),-Float::MANT_DIG) - x - dx - else - x - dx/2 # x - Math.ldexp(Math.ldexp(1.0,-Float::MANT_DIG),e-1) - end - end - - end - - module BurgerDybvig # :nodoc: all - module_function - - def float_to_digits(v,f,e,round_mode,min_e,p,b,_B) - - case round_mode - when :even - roundl = roundh = f.even? - when :inf - roundl = true - roundh = false - when :zero - roundl = false - roundh = true - else - # here we don't assume any rounding in the floating point numbers - # the result is valid for any rounding but may produce more digits - # than stricly necessary for specifica rounding modes. - roundl = false - roundh = false - end - - if e >= 0 - if f != exptt(b,p-1) - be = exptt(b,e) - r,s,m_p,m_m,k = scale(f*be*2,2,be,be,0,_B,roundl ,roundh,v) - else - be = exptt(b,e) - be1 = be*b - r,s,m_p,m_m,k = scale(f*be1*2,b*2,be1,be,0,_B,roundl ,roundh,v) - end - else - if e==min_e or f != exptt(b,p-1) - r,s,m_p,m_m,k = scale(f*2,exptt(b,-e)*2,1,1,0,_B,roundl ,roundh,v) - else - r,s,m_p,m_m,k = scale(f*b*2,exptt(b,1-e)*2,b,1,0,_B,roundl ,roundh,v) - end - end - [k]+generate(r,s,m_p,m_m,_B,roundl ,roundh) - end - - def scale(r,s,m_p,m_m,k,_B,low_ok ,high_ok,v) - return scale2(r,s,m_p,m_m,k,_B,low_ok ,high_ok) if v==0 - est = (logB(_B,v)-1E-10).ceil.to_i - if est>=0 - fixup(r,s*exptt(_B,est),m_p,m_m,est,_B,low_ok,high_ok) - else - sc = exptt(_B,-est) - fixup(r*sc,s,m_p*sc,m_m*sc,est,_B,low_ok,high_ok) - end - end - - def fixup(r,s,m_p,m_m,k,_B,low_ok,high_ok) - if (high_ok ? (r+m_p >= s) : (r+m_p > s)) # too low? - [r,s*_B,m_p,m_m,k+1] - else - [r,s,m_p,m_m,k] - end - end - - def scale2(r,s,m_p,m_m,k,_B,low_ok ,high_ok) - loop do - if (high_ok ? (r+m_p >= s) : (r+m_p > s)) # k is too low - s *= _B - k += 1 - elsif (high_ok ? ((r+m_p)*_B<s) : ((r+m_p)*_B<=s)) # k is too high - r *= _B - m_p *= _B - m_m *= _B - k -= 1 - else - break - end - end - [r,s,m_p,m_m,k] - end - - def generate(r,s,m_p,m_m,_B,low_ok ,high_ok) - list = [] - loop do - d,r = (r*_B).divmod(s) - m_p *= _B - m_m *= _B - tc1 = low_ok ? (r<=m_m) : (r<m_m) - tc2 = high_ok ? (r+m_p >= s) : (r+m_p > s) - - if not tc1 - if not tc2 - list << d - else - list << d+1 - break - end - else - if not tc2 - list << d - break - else - if r*2 < s - list << d - break - else - list << d+1 - break - end - end - end - - end - list - end - - $exptt_table = Array.new(326) - (0...326).each{|i| $exptt_table[i]=10**i} - def exptt(_B, k) - if _B==10 && k>=0 && k<326 - $exptt_table[k] - else - _B**k - end - end - - $logB_table = Array.new(37) - (2...37).each{|b| $logB_table[b]=1.0/Math.log(b)} - def logB(_B, x) - if _B>=2 && _B<37 - Math.log(x)*$logB_table[_B] - else - Math.log(x)/Math.log(_B) - end - end - - def float_to_digits_max(v,f,e,round_mode,min_e,p,b,_B) - - case round_mode - when :even - roundl = roundh = f.even? - when :inf - roundl = true - roundh = false - when :zero - roundl = false - roundh = true - else - # here we don't assume any rounding in the floating point numbers - # the result is valid for any rounding but may produce more digits - # than stricly necessary for specifica rounding modes. - roundl = false - roundh = false - end - - if e >= 0 - if f != exptt(b,p-1) - be = exptt(b,e) - r,s,m_p,m_m,k = scale(f*be*2,2,be,be,0,_B,roundl ,roundh,v) - else - be = exptt(b,e) - be1 = be*b - r,s,m_p,m_m,k = scale(f*be1*2,b*2,be1,be,0,_B,roundl ,roundh,v) - end - else - if e==min_e or f != exptt(b,p-1) - r,s,m_p,m_m,k = scale(f*2,exptt(b,-e)*2,1,1,0,_B,roundl ,roundh,v) - else - r,s,m_p,m_m,k = scale(f*b*2,exptt(b,1-e)*2,b,1,0,_B,roundl ,roundh,v) - end - end - [k]+generate_max(r,s,m_p,m_m,_B,roundl ,roundh) - end - - def generate_max(r,s,m_p,m_m,_B,low_ok ,high_ok) - list = [false] - loop do - d,r = (r*_B).divmod(s) - m_p *= _B - m_m *= _B - - list << d - - tc1 = low_ok ? (r<=m_m) : (r<m_m) - tc2 = high_ok ? (r+m_p >= s) : (r+m_p > s) - - if tc1 && tc2 - list[0] = true if r*2 >= s - break - end - end - list - end - - end - end class Float include Nio::Formattable - def self.nio_read_neutral(neutral) + def self.nio_read_neutral(neutral) x = nil honor_rounding = true if neutral.special? @@ -1548,17 +1293,17 @@ when :nan x = 0.0/0.0 when :inf x = (neutral.sign=='-' ? -1.0 : +1.0)/0.0 end - elsif neutral.rep_pos<neutral.digits.length + elsif neutral.rep_pos<neutral.digits.length x,y = neutral.to_RepDec.getQ x = Float(x)/y else - nd = neutral.base==10 ? Float::DIG : ((Float::MANT_DIG-1)*Math.log(2)/Math.log(neutral.base)).floor + nd = neutral.base==10 ? Float::DIG : ((Float::MANT_DIG-1)*Math.log(2)/Math.log(neutral.base)).floor k = neutral.dec_pos-neutral.digits.length if !honor_rounding && (neutral.digits.length<=nd && k.abs<=15) x = neutral.digits.to_i(neutral.base).to_f if k<0 x /= Float(neutral.base**-k) @@ -1574,28 +1319,66 @@ elsif neutral.base.modulo(Float::RADIX)==0 f = neutral.digits.to_i(neutral.base) e = neutral.dec_pos-neutral.digits.length - rounding = neutral.rounding + rounding = case neutral.rounding + when :even + :half_even + when :zero + :half_down + when :inf + :half_up + when :truncate + :down + when :directed_up + :up + when :floor + :floor + when :ceil + :ceil + else + nil + end - x = Nio::Clinger::algM(f,e,rounding,neutral.base,Float::RADIX,Float::MANT_DIG,Float::MIN_EXP-Float::MANT_DIG,Float::MAX_EXP-Float::MANT_DIG) - x = -x if neutral.sign=='-' + reader = Flt::Support::Reader.new(:mode=>:fixed) + sign = neutral.sign == '-' ? -1 : +1 + x = reader.read(Float.context, rounding, sign, f, e, neutral.base) + exact = reader.exact? else f = neutral.digits.to_i(neutral.base) e = neutral.dec_pos-neutral.digits.length - rounding = neutral.rounding + rounding = case neutral.rounding + when :even + :half_even + when :zero + :half_down + when :inf + :half_up + when :truncate + :down + when :directed_up + :up + when :floor + :floor + when :ceil + :ceil + else + nil + end - x = Nio::Clinger::algM(f,e,rounding,neutral.base,Float::RADIX,Float::MANT_DIG,Float::MIN_EXP-Float::MANT_DIG,Float::MAX_EXP-Float::MANT_DIG) - x = -x if neutral.sign=='-' + reader = Flt::Support::Reader.new(:mode=>:fixed) + sign = neutral.sign == '-' ? -1 : +1 + x = reader.read(Float.context, rounding, sign, f, e, neutral.base) + exact = reader.exact? end end - + return x end def nio_write_neutral(fmt) neutral = Nio::NeutralNum.new x = self @@ -1607,26 +1390,26 @@ else converted = false if fmt.get_ndig==:exact && fmt.get_approx==:simplify if x!=0 - q = x.nio_r(Nio::Tolerance.decimals(Float::DIG,:sig)) + q = x.nio_r(Flt.Tolerance(Float::DIG, :sig_decimals)) if q!=0 neutral = q.nio_write_neutral(fmt) converted = true if neutral.digits.length<=Float::DIG end end elsif fmt.get_approx==:exact neutral = x.nio_xr.nio_write_neutral(fmt) converted = true end - if !converted + if !converted if fmt.get_base==10 && false txt = format "%.*e",Float::DECIMAL_DIG-1,x # note that spec. e output precision+1 significant digits - sign = '+' + sign = '+' if txt[0,1]=='-' sign = '-' txt = txt[1...txt.length] end exp = 0 @@ -1634,16 +1417,16 @@ exp_i = txt.index(x_char) exp_i = txt.index(x_char.downcase) if exp_i===nil if exp_i!=nil exp = txt[exp_i+1...txt.length].to_i - txt = txt[0...exp_i] - end + txt = txt[0...exp_i] + end dec_pos = txt.index '.' if dec_pos==nil - dec_pos = txt.length + dec_pos = txt.length else txt[dec_pos]='' end dec_pos += exp neutral.set sign, txt, dec_pos, nil, fmt.get_base_digits(10), true, fmt.get_round @@ -1652,11 +1435,10 @@ end end if !converted sign = x<0 ? '-' : '+' - x = -x if sign=='-' f,e = Math.frexp(x) if e < Float::MIN_EXP # denormalized number f = Math.ldexp(f,e-Float::MIN_EXP+Float::MANT_DIG) e = Float::MIN_EXP-Float::MANT_DIG @@ -1666,91 +1448,94 @@ e -= Float::MANT_DIG end f = f.to_i inexact = true - rounding = fmt.get_round - - if fmt.get_all_digits? - # use as many digits as possible - dec_pos,r,*digits = Nio::BurgerDybvig::float_to_digits_max(x,f,e,rounding,Float::MIN_EXP-Float::MANT_DIG,Float::MANT_DIG,Float::RADIX,fmt.get_base) - inexact = :roundup if r + rounding = case fmt.get_round + when :even + :half_even + when :zero + :half_down + when :inf + :half_up + when :truncate + :down + when :directed_up + :up + when :floor + :floor + when :ceil + :ceil else - # use as few digits as possible - dec_pos,*digits = Nio::BurgerDybvig::float_to_digits(x,f,e,rounding,Float::MIN_EXP-Float::MANT_DIG,Float::MANT_DIG,Float::RADIX,fmt.get_base) + nil end + + + # Note: it is assumed that fmt will be used for for input too, otherwise + # rounding should be Float.context.rounding (input rounding for Float) rather than fmt.get_round (output) + formatter = Flt::Support::Formatter.new(Float::RADIX, Float::MIN_EXP-Float::MANT_DIG, fmt.get_base) + formatter.format(x, f, e, rounding, Float::MANT_DIG, fmt.get_all_digits?) + inexact = formatter.round_up if formatter.round_up.is_a?(Symbol) + dec_pos, digits = formatter.digits txt = '' digits.each{|d| txt << fmt.get_base_digits.digit_char(d)} neutral.set sign, txt, dec_pos, nil, fmt.get_base_digits, inexact, fmt.get_round end end - + return neutral - end -end - -class Numeric - unless method_defined?(:even?) - def even? - self.modulo(2)==0 - end end - unless method_defined?(:odd?) - def odd? - self.modulo(2)!=0 - end - end end class Integer include Nio::Formattable - def self.nio_read_neutral(neutral) + def self.nio_read_neutral(neutral) x = nil if neutral.special? raise Nio::InvalidFormat,"Invalid integer numeral" - elsif neutral.rep_pos<neutral.digits.length + elsif neutral.rep_pos<neutral.digits.length return Rational.nio_read_neutral(neutral).to_i else digits = neutral.digits if neutral.dec_pos <= 0 digits = '0' elsif neutral.dec_pos <= digits.length digits = digits[0...neutral.dec_pos] else - digits = digits + '0'*(neutral.dec_pos-digits.length) + digits = digits + '0'*(neutral.dec_pos-digits.length) end x = digits.to_i(neutral.base) # this was formely needed because we didn't adust the digits # if neutral.dec_pos != neutral.digits.length # # with rational included, negative powers of ten are rational numbers - # x = (x*((neutral.base)**(neutral.dec_pos-neutral.digits.length))).to_i + # x = (x*((neutral.base)**(neutral.dec_pos-neutral.digits.length))).to_i # end x = -x if neutral.sign=='-' end - + return x end def nio_write_neutral(fmt) neutral = Nio::NeutralNum.new x = self sign = x<0 ? '-' : '+' txt = x.abs.to_s(fmt.get_base) - dec_pos = rep_pos = txt.length + dec_pos = rep_pos = txt.length neutral.set sign, txt, dec_pos, nil, fmt.get_base_digits, false ,fmt.get_round - + return neutral end end class Rational include Nio::Formattable - def self.nio_read_neutral(neutral) + def self.nio_read_neutral(neutral) x = nil if neutral.special? case neutral.special when :nan @@ -1759,19 +1544,19 @@ x = Rational((neutral.sign=='-' ? -1 : +1),0) end else x = Rational(*neutral.to_RepDec.getQ) end - + return x end def nio_write_neutral(fmt) neutral = Nio::NeutralNum.new x = self if x.denominator==0 - if x.numerator>0 + if x.numerator>0 neutral.set_special(:inf) elsif x.numerator<0 neutral.set_special(:inf,'-') else neutral.set_special(:nan) @@ -1784,29 +1569,29 @@ rd = Nio::RepDec.new.setQ(x.numerator,x.denominator, opt) end neutral = rd.to_NeutralNum(fmt.get_base_digits) neutral.rounding = fmt.get_round end - + return neutral end end if defined? BigDecimal class BigDecimal include Nio::Formattable - def self.nio_read_neutral(neutral) + def self.nio_read_neutral(neutral) x = nil if neutral.special? case neutral.special when :nan x = BigDecimal('NaN') # BigDecimal("0")/0 when :inf x = BigDecimal(neutral.sign=='-' ? '-1.0' : '+1.0')/0 end - elsif neutral.rep_pos<neutral.digits.length + elsif neutral.rep_pos<neutral.digits.length x,y = neutral.to_RepDec.getQ x = BigDecimal(x.to_s)/y else @@ -1817,46 +1602,46 @@ str = neutral.sign str += neutral.digits str += "E#{(neutral.dec_pos-neutral.digits.length)}" x = BigDecimal(str) else - x = BigDecimal(neutral.digits.to_i(neutral.base).to_s) + x = BigDecimal(neutral.digits.to_i(neutral.base).to_s) x *= BigDecimal(neutral.base.to_s)**(neutral.dec_pos-neutral.digits.length) x = -x if neutral.sign=='-' end end - + return x end def nio_write_neutral(fmt) neutral = Nio::NeutralNum.new x = self if x.nan? neutral.set_special(:nan) elsif x.infinite? neutral.set_special(:inf, x<0 ? '-' : '+') - else + else converted = false if fmt.get_ndig==:exact && fmt.get_approx==:simplify prc = [x.precs[0],20].max - neutral = x.nio_r(Nio::BigTolerance.decimals(prc,:sig)).nio_write_neutral(fmt) + neutral = x.nio_r(Flt.Tolerance(prc, :sig_decimals)).nio_write_neutral(fmt) converted = true if neutral.digits.length<prc elsif fmt.get_approx==:exact && fmt.get_base!=10 neutral = x.nio_xr.nio_write_neutral(fmt) converted = true end - if !converted - if fmt.get_base==10 + if !converted + if fmt.get_base==10 # Don't use x.to_s because of bugs in BigDecimal in Ruby 1.9 revisions 20359-20797 # x.to_s('F') is not affected by that problem, but produces innecesary long strings sgn,ds,b,e = x.split txt = "#{sgn<0 ? '-' : ''}0.#{ds}E#{e}" - sign = '+' + sign = '+' if txt[0,1]=='-' sign = '-' txt = txt[1...txt.length] end exp = 0 @@ -1864,57 +1649,213 @@ exp_i = txt.index(x_char) exp_i = txt.index(x_char.downcase) if exp_i===nil if exp_i!=nil exp = txt[exp_i+1...txt.length].to_i - txt = txt[0...exp_i] - end + txt = txt[0...exp_i] + end dec_pos = txt.index '.' if dec_pos==nil - dec_pos = txt.length + dec_pos = txt.length else txt[dec_pos]='' end dec_pos += exp neutral.set sign, txt, dec_pos, nil, fmt.get_base_digits(10), true, fmt.get_round converted = true end - end + end if !converted - min_prec = 24 - min_exp = -1000 - s,f,b,e = x.split - e -= f.size - sign = s<0 ? '-' : '+' - x = -x if sign=='-' - f_i = f.to_i - prc = [x.precs[0],min_prec].max - f_i *= 10**(prc-f.size) - e -= (prc-f.size) + x = Flt::DecNum(x.to_s) + min_exp = num_class.context.etiny + n = x.number_of_digits + s,f,e = x.split + b = num_class.radix + if s < 0 + sign = '-' + else + sign = '+' + end + prc = x.number_of_digits + f = num_class.int_mult_radix_power(f, prc-n) + e -= (prc-n) + inexact = true - rounding = fmt.get_round - - if fmt.get_all_digits? - # use as many digits as possible - dec_pos,r,*digits = Nio::BurgerDybvig::float_to_digits_max(x,f_i,e,rounding,[e,min_exp].min,prc,b,fmt.get_base) - inexact = :roundup if r + rounding = case fmt.get_round + when :even + :half_even + when :zero + :half_down + when :inf + :half_up + when :truncate + :down + when :directed_up + :up + when :floor + :floor + when :ceil + :ceil else - # use as few digits as possible - dec_pos,*digits = Nio::BurgerDybvig::float_to_digits(x,f_i,e,rounding,[e,min_exp].min,prc,b,fmt.get_base) + nil end + + + # TODO: use Num#format instead + # Note: it is assumed that fmt will be used for for input too, otherwise + # rounding should be Float.context.rounding (input rounding for Float) rather than fmt.get_round (output) + formatter = Flt::Support::Formatter.new(num_class.radix, num_class.context.etiny, fmt.get_base) + formatter.format(x, f, e, rounding, prc, fmt.get_all_digits?) + inexact = formatter.round_up if formatter.round_up.is_a?(Symbol) + dec_pos,digits = formatter.digits txt = '' digits.each{|d| txt << fmt.get_base_digits.digit_char(d)} neutral.set sign, txt, dec_pos, nil, fmt.get_base_digits, inexact, fmt.get_round + end end - + return neutral end end +end + +class Flt::Num + include Nio::Formattable + def self.nio_read_neutral(neutral) + x = nil + + if neutral.special? + case neutral.special + when :nan + x = num_class.nan + when :inf + x = num_class.infinity(neutral.sign=='-' ? '-1.0' : '+1.0') + end + elsif neutral.rep_pos<neutral.digits.length + + # uses num_clas.context.precision TODO: ? + x = num_class.new Rational(*neutral.to_RepDec.getQ) + + else + if neutral.base==num_class.radix + if neutral.base==10 + str = neutral.sign + str += neutral.digits + str += "E#{(neutral.dec_pos-neutral.digits.length)}" + x = num_class.new(str) + else + f = neutral.digits.to_i(neutral.base) + e = neutral.dec_pos-neutral.digits.length + s = neutral.sign=='-' ? -1 : +1 + x = num_class.Num(s, f, e) + end + else + # uses num_clas.context.precision TODO: ? + if num_class.respond_to?(:power) + x = num_class.Num(neutral.digits.to_i(neutral.base).to_s) + x *= num_class.Num(neutral.base.to_s)**(neutral.dec_pos-neutral.digits.length) + x = -x if neutral.sign=='-' + else + + # uses num_clas.context.precision TODO: ? + x = num_class.new Rational(*neutral.to_RepDec.getQ) + + end + end + end + + return x + end + def nio_write_neutral(fmt) + neutral = Nio::NeutralNum.new + x = self + + if x.nan? + neutral.set_special(:nan) + elsif x.infinite? + neutral.set_special(:inf, x<0 ? '-' : '+') + else + converted = false + if fmt.get_ndig==:exact && fmt.get_approx==:simplify + + neutral = x.nio_r(Flt.Tolerance('0.5', :ulps)).nio_write_neutral(fmt) + # TODO: find better method to accept the conversion + prc = (fmt.get_base==num_class.radix) ? x.number_of_digits : x.coefficient.to_s(fmt.get_base).length + prc = [prc, 8].max + converted = true if neutral.digits.length<prc + + elsif fmt.get_approx==:exact && fmt.get_base!=num_class.radix + # TODO: num_class.context(:precision=>fmt.... + neutral = x.to_r.nio_write_neutral(fmt) + converted = true + end + if !converted + if fmt.get_base==num_class.radix + sign = x.sign==-1 ? '-' : '+' + txt = x.coefficient.to_s(fmt.get_base) # TODO: can use x.digits directly? + dec_pos = rep_pos = x.fractional_exponent + neutral.set sign, txt, dec_pos, nil, fmt.get_base_digits, false ,fmt.get_round + converted = true + end + end + if !converted + + min_exp = num_class.context.etiny + n = x.number_of_digits + s,f,e = x.split + b = num_class.radix + if s < 0 + sign = '-' + else + sign = '+' + end + prc = x.number_of_digits + f = num_class.int_mult_radix_power(f, prc-n) + e -= (prc-n) + + inexact = true + + rounding = case fmt.get_round + when :even + :half_even + when :zero + :half_down + when :inf + :half_up + when :truncate + :down + when :directed_up + :up + when :floor + :floor + when :ceil + :ceil + else + nil + end + + + # TODO: use Num#format instead + # Note: it is assumed that fmt will be used for for input too, otherwise + # rounding should be Float.context.rounding (input rounding for Float) rather than fmt.get_round (output) + formatter = Flt::Support::Formatter.new(num_class.radix, num_class.context.etiny, fmt.get_base) + formatter.format(x, f, e, rounding, prc, fmt.get_all_digits?) + inexact = formatter.round_up if formatter.round_up.is_a?(Symbol) + dec_pos,digits = formatter.digits + txt = '' + digits.each{|d| txt << fmt.get_base_digits.digit_char(d)} + neutral.set sign, txt, dec_pos, nil, fmt.get_base_digits, inexact, fmt.get_round + + end + end + + return neutral + end end