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

- old
+ new

@@ -1,1883 +1,1917 @@ -# Formatting numbers as text - -# 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' - -require 'nio/repdec' - -require 'nio/rtnlzr' - -require 'rational' - -require 'bigdecimal' - -module Nio - - # positional notation, unformatted numeric literal: used as intermediate form - 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 - attr_writer :sign, :digits, :dec_pos, :rep_pos, :special, :inexact, :rounding - - # set number - def set(s,d,p=nil,r=nil,dgs=DigitsDef.base(10),inexact=false,rounding=:inf,normalize=true) - @sign = s # sign: '+','-','' - @digits = d # digits string - @dec_pos = p==nil ? d.length : p # position of decimal point: 0=before first digit... - @rep_pos = r==nil ? d.length : r # first repeated digit (0=first digit...) - @dgs = dgs - @base = @dgs.radix - @inexact = inexact - @special = nil - @rounding = rounding - trimZeros unless inexact - self - end - # set infinite (:inf) and invalid (:nan) numbers - def set_special(s,sgn='') # :inf, :nan - @special = s - @sign = sgn - self - end - - def base - @base - end - def base_digits - @dgs - end - def base_digits=(dd) - @dgs = dd - @base = @dgs.radix - end - def base=(b) - @dgs = DigitsDef.base(b) - @base=@dgs.radix - end - - # check for special numbers (which have only special and sign attributes) - def special? - special != nil - end - - # check for special numbers (which have only special and sign attributes) - def inexact? - @inexact - end - - def dup - n = NeutralNum.new - if special? - n.set_special @special.dup, @sign.dup - 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 - end - - def zero? - z = false - if !special - if digits=='' - z = true - else - z = true - for i in (0...@digits.length) - if dig_value(i)!=0 - z = false - break - end - end - end - end - z - end - - def round!(n, mode=:fix, dir=nil) - dir ||= rounding - trimLeadZeros - if n==:exact - return unless @inexact - n = @digits.size - end - - n += @dec_pos if mode==:fix - n = [n,@digits.size].min if @inexact - - adj = 0 - dv = :tie - if @inexact && n==@digits.size - dv = @inexact==:roundup ? :hi : :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 - end - end - - if dv==:hi - adj = +1 - elsif dv==:tie - if dir==:inf # towards nearest +/-infinity - 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 - # adj = +1 unless (dig_value(n-1)%2)!=0 - end - end - - if n>@digits.length - (@digits.length...n).each do |i| - @digits << dig_char(dig_value(i)) - @rep_pos += 1 - end - end - - prefix = '' - i = n-1 - while adj!=0 - v = dig_value(i) - v += adj - adj = 0 - if v<0 - v += @base - adj = -1 - elsif v>=@base - v -= @base - adj = +1 - end - if i<0 - prefix = dig_char(v)+prefix - elsif i<@digits.length - @digits[i] = dig_char(v) - end - i += -1 - end - - if n<0 - @digits = "" - else - @digits = @digits[0...n] - end - @rep_pos = @digits.length - - if prefix!='' - @digits = prefix + @digits - @dec_pos += prefix.length - @rep_pos += prefix.length - end - - - end - - def round(n, mode=:fix, dir=nil) - dir ||= rounding - nn = dup - nn.round!(n,mode,dir) - return nn - end - - def trimTrailZeros() - i = @digits.length - while i>0 && dig_value(i-1)==0 - i -= 1 - end - if @rep_pos>=i - @digits = @digits[0...i] - @rep_pos = i - end - - if @digits=='' - @digits = dig_char(0) # '0' - @rep_pos = 1 - @dec_pos = 1 - end - - end - - def trimLeadZeros() - i = 0 - while i<@digits.length && dig_value(i)==0 - i += 1 - end - @digits = @digits[i...@digits.length] - @dec_pos -= i - @rep_pos -= i - - if @digits=='' - @digits = dig_char(0) # '0' - @rep_pos = 1 - @dec_pos = 1 - end - - end - - def trimZeros() - trimLeadZeros - trimTrailZeros - end - - protected - - def dig_value(i) - v = 0 - if i>=@rep_pos - i -= @digits.length - i %= @digits.length - @rep_pos if @rep_pos<@digits.length - i += @rep_pos - 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 - #def digcode_value(c) - # v = c-?0 - # if v>9 - # v = 10 + c.chr.downcase[0] - ?a - # end - # v - # @dgs.digit_value(c) - #end - - def dig_char(v) - c = '' - if v!=nil && v>=0 && v<@base - c = @dgs.digit_char(v).chr - end - c - end - - end - - class NeutralNum - public - def to_RepDec - n = RepDec.new(@base) - if special? - - case special - when :nan - n.ip = :indeterminate - when :inf - if sign=='-' - n.ip = :posinfinity - else - n.ip :neginfinity - end - else - n = nil - end - - 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 - 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 - i=0 if i>=digits.length-rep_pos - end - n.d = [] - while i<digits.length-rep_pos - n.d << @dgs.digit_value(digits[rep_pos+i]) - i += 1 - end - new_rep_pos = n.d.size + dec_pos - n.d += text_to_digits(digits[rep_pos..-1]) - self.rep_pos = new_rep_pos - else - n.ip *= @base**(dec_pos-digits.length) - 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 - 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 - n.sign = -1 if sign=='-' - 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)} - ds = [] - txt.each_byte{|b| ds << @dgs.digit_value(b)} - ds - end - end - - class RepDec - public - def to_NeutralNum(base_dgs=nil) - num = NeutralNum.new - if !ip.is_a?(Integer) - - case ip - when :indeterminate - num.set_special :nan - when :posinfinity - num.set_special :inf,'+' - 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) - dec_pos = decimals.length - 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 - - # A Fmt object defines a numeric format. - # - # The formatting aspects managed by Fmt are: - # * mode and precision - # - #mode() and #orec() set the main paramters - # - see also #show_all_digits(), #approx_mode(), #insignificant_digits(), - # #sci_digits() and #show_plus() - # * separators - # - see #sep() and #grouping() - # * field justfification - # - #width() and the shortcut #pad0s() - # * numerical base - # - #base() - # * 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. - # - # This class also contains class methods for numeric conversion: - # * Fmt.convert - # and for default and other predefined formats: - # * Fmt.default / Fmt.default= - # * Fmt.[] / Fmt.[]= - # - # The actual formatted reading and writting if performed by - # * #nio_write() (Nio::Formattable#nio_write) - # * #nio_read() (Nio::Formattable::ClassMethods#nio_read) - # Finally numerical objects can be rounded according to a format: - # * #nio_round() (Nio::Formattable#nio_round) - class Fmt - include StateEquivalent - - class Error < StandardError # :nodoc: - end - class InvalidOption < Error # :nodoc: - end - class InvalidFormat < Error # :nodoc: - end - - @@default_rounding_mode = :even - def initialize() - - @dec_sep = '.' - @grp_sep = ',' - @grp = [] - - @ndig = :exact - @mode=:gen - @round=Fmt.default_rounding_mode - @all_digits = false - @approx = :only_sig - @non_sig = '' # marker for insignificant digits of inexact values e.g. '#','0' - @sci_format = 1 # number of integral digits in the mantissa: -1 for all - - @show_plus = false - - @rep_begin = '<' - @rep_end = '>' - @rep_auto = '...' - @rep_n = 2 - @rep_in = true - - @width = 0 - @fill_char = ' ' - @adjust=:right - - @base_radix = 10 - @base_uppercase = true - @base_digits = DigitsDef.base(@base_radix, !@base_uppercase) - @show_base = false - @base_indicators = { 2=>'b', 8=>'o', 10=>'', 16=>'h', 0=>'r'} # 0: generic (used with radix) - @base_prefix = false - - @nan_txt = 'NAN' - @inf_txt = 'Infinity' - - yield self if block_given? - end - - # Defines the separators used in numerals. This is relevant to - # 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. - # - # Finally, the third argument is an array that defines the groups - # of digits to separate. - # By default it's [], which means that no grouping will be produced on output - # (but the group separator defined will be ignored in input.) - # To produce the common thousands separation a value of [3] must be passed, - # which means that groups of 3 digits are used. - def sep(dec_sep,grp_sep=nil,grp=nil) - dup.sep!(dec_sep,grp_sep,grp) - end - # This is the mutator version of #sep(). - def sep!(dec_sep,grp_sep=nil,grp=nil) - set! :dec_sep=>dec_sep, :grp_sep=>grp_sep, :grp=>grp - end - - # This defines the grouping of digits (which can also be defined in #sep() - def grouping(grp=[3],grp_sep=nil) - dup.grouping!(grp,grp_sep) - end - # 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 - # 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 - # and define the grouping as with #grouping(). - def Fmt.grouping(grp=[3],grp_sep=nil) - Fmt.default.grouping(grp,grp_sep) - end - - # Define the formatting mode. There are two fixed parameters: - # - <tt>mode</tt> (only relevant for output) - # [<tt>:gen</tt>] - # (general) chooses automatically the shortes format - # [<tt>:fix</tt>] - # (fixed precision) is a simple format with a fixed number of digits - # after the point - # [<tt>:sig</tt>] - # (significance precision) is like :fix but using significant digits - # [<tt>:sci</tt>] - # (scientific) is the exponential form 1.234E2 - # - <tt>precision</tt> (number of digits or :exact, only used for output) - # [<tt>exact</tt>] - # means that as many digits as necessary to unambiguosly define the - # value are used; this is the default. - # - # 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; - # 1.5 is rounded to 2, -1.5 to -2 - # [<tt>:zero</tt>] - # 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 - # [<tt>:only_sig</tt>] - # (the default) treats the value as an approximation and only - # significant digits (those that cannot take an arbitrary value without - # changing the specified value) are shown. - # [<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 - # 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={}) - dup.mode!(mode,precision,options) - end - # This is the mutator version of #mode(). - def mode!(mode,precision=nil,options={}) - set! options.merge(:mode=>mode, :ndig=>precision) - end - - # 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 - # 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 - # 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 - # and define the formatting mode as with #prec() - def Fmt.prec(ndig,mode=nil,options={}) - Fmt.default.prec(ndig,mode,options) - end - - # Rounding mode used when not specified otherwise - def Fmt.default_rounding_mode - @@default_rounding_mode - end - # The default rounding can be changed here; it starts with the value :even. - # See the rounding modes available in the description of method #mode(). - def Fmt.default_rounding_mode=(m) - @@default_rounding_mode=m - 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 - # This is the mutator version of #show_all_digits(). - def show_all_digits!(ad=true) - set! :all_digits=>ad - end - # This defines the approximate mode (:only_sig, :exact, :simplify) - # just like the last parameter of #mode() - def approx_mode(mode) - dup.approx_mode! mode - end - # This is the mutator version of #approx_mode(). - def approx_mode!(mode) - set! :approx=>mode - end - # Defines a character to stand for insignificant digits when - # a specific number of digits has been requested greater than then - # number of significant digits (for approximate types). - def insignificant_digits(ch='#') - dup.insignificant_digits! ch - end - # This is the mutator version of #insignificant_digits(). - def insignificant_digits!(ch='#') - ch ||= '' - set! :non_sig=>ch - end - # Defines the number of significan digits before the radix separator - # in scientific notation. A negative value will set all significant digits - # before the radix separator. The special value <tt>:eng</tt> activates - # _engineering_ mode, in which the exponents are multiples of 3. - # - # For example: - # 0.1234.nio_write(Fmt.mode(:sci,4).sci_digits(0) -> 0.1234E0 - # 0.1234.nio_write(Fmt.mode(:sci,4).sci_digits(3) -> 123.4E-3 - # 0.1234.nio_write(Fmt.mode(:sci,4).sci_digits(-1) -> 1234.E-4 - # 0.1234.nio_write(Fmt.mode(:sci,4).sci_digits(:eng) -> 123.4E-3 - def sci_digits(n=-1) - dup.sci_digits! n - end - # 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 - # 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 - # 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 - # 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 - # and define sci_digits - def Fmt.sci_digits(v=-1) - Fmt.default.sci_digits(v) - end - - # Controls the display of the sign for positive numbers - def show_plus(sp=true) - dup.show_plus! sp - end - # This is the mutator version of #show_plus(). - def show_plus!(sp=true) - set! :show_plus=>sp - end - - # 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 - - # Defines the handling and notation for repeating numerals. The parameters - # can be passed in order or in a hash: - # [<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; - # 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) - - params << {} if params.size==0 - if params[0].kind_of?(Hash) - params = params[0] - 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 - # and define the repeating decimals mode as with #rep() - def Fmt.rep(*params) - Fmt.default.rep(*params) - end - - # Sets the justificaton width, mode and fill character - # - # The mode accepts these values: - # [<tt>:right</tt>] (the default) justifies to the right (adds padding at the left) - # [<tt>:left</tt>] justifies to the left (adds padding to the right) - # [<tt>:internal</tt>] like :right, but the sign is kept to the left, outside the padding. - # [<tt>:center</tt>] centers the number in the field - def width(w,adj=nil,ch=nil) - dup.width! w,adj,ch - end - # This is the mutator version of #width(). - def width!(w,adj=nil,ch=nil) - set! :width=>w, :adjust=>adj, :fill_char=>ch - end - # Defines the justification (as #width()) with the given - # width, internal mode and filling with zeros. - # - # Note that if you also use grouping separators, the filling 0s - # will not be separated. - def pad0s(w) - dup.pad0s! w - end - # This is the mutator version of #pad0s(). - def pad0s!(w) - width! w, :internal, '0' - end - # This is a shortcut to create a new Fmt object and define the width - # parameters as with #widht() - def Fmt.width(w,adj=nil,ch=nil) - Fmt.default.width(w,adj,ch) - end - # This is a shortcut to create a new Fmt object and define numeric - # padding as with #pad0s() - def Fmt.pad0s(w) - Fmt.default.pad0s(w) - end - - # defines the numerical base; the second parameters forces the use - # of uppercase letters for bases greater than 10. - def base(b, uppercase=nil) - dup.base! b, uppercase - end - # This is the mutator version of #base(). - def base!(b, uppercase=nil) - set! :base_radix=>b, :base_uppercase=>uppercase - end - # This is a shortcut to create a new Fmt object and define the base - def Fmt.base(b, uppercase=nil) - Fmt.default.base(b, uppercase) - end - # returns the exponent char used with the specified base - def get_exp_char(base) # :nodoc: - base ||= @base_radix - base<=10 ? 'E' : '^' - end - - # returns the base - def get_base # :nodoc: - @base_radix - end - # returns the digit characters used for a base - def get_base_digits(b=nil) # :nodoc: - (b.nil? || b==@base_radix) ? @base_digits : DigitsDef.base(b,!@base_uppercase) - end - # returns true if uppercase digits are used - def get_base_uppercase? # :nodoc: - @base_uppercase - end - - # returns the formatting mode - def get_mode # :nodoc: - @mode - end - # returns the precision (number of digits) - def get_ndig # :nodoc: - @ndig - end - # return the show_all_digits state - def get_all_digits? # :nodoc: - @all_digits - end - # returns the approximate mode - def get_approx # :nodoc: - @approx - end - - # returns the rounding mode - def get_round # :nodoc: - @round - end - - # Method used internally to format a neutral numeral - def nio_write_formatted(neutral) # :nodoc: - str = '' - if neutral.special? - str << neutral.sign - case neutral.special - when :inf - str << @inf_txt - when :nan - str << @nan_txt - end - else - 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 - if @ndig.kind_of?(Numeric) && @ndig>0 && @mode==:fix - str << @dec_sep << zero*@ndig - end - else - - neutral.trimLeadZeros - actual_mode = @mode - trim_trail_zeros = !@all_digits # false - - integral_digits = @sci_format - if integral_digits == :eng - integral_digits = 1 - while (neutral.dec_pos - integral_digits).modulo(3) != 0 - integral_digits += 1 - end - elsif integral_digits==:all || integral_digits < 0 - if neutral.inexact? && @non_sig!='' && @ndig.kind_of?(Numeric) - integral_digits = @ndig - 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 - when :fix, :sig #, :gen - - str << neutral.sign if @show_plus || neutral.sign!='+' - - if @show_base && @base_prefix - b_prefix = @base_indicators[neutral.base] - str << b_prefix if b_prefix - end - - if @ndig==:exact - neutral.sign = '+' - str << neutral.to_RepDec.getS(@rep_n, getRepDecOpt(neutral.base)) - else - #zero = get_base_digits.digit_char(0).chr - ns_digits = '' - - nd = neutral.digits.length - if actual_mode==:fix - nd -= neutral.dec_pos - end - if neutral.inexact? && @ndig>nd # assert no rep-dec. - ns_digits = @non_sig*(@ndig-nd) - 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 - 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 - - #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 - str.chop! while str[-1]==zero[0] - str.chomp!(@dec_sep) - #puts str - end - - - when :sci - - str << neutral.sign if @show_plus || neutral.sign!='+' - - if @show_base && @base_prefix - b_prefix = @base_indicators[neutral.base] - str << b_prefix if b_prefix - end - - #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)) - else - ns_digits = '' - - nd = neutral.digits.length - if actual_mode==:fix - nd -= neutral.dec_pos - end - if neutral.inexact? && @ndig>nd # assert no rep-dec. - ns_digits = @non_sig*(@ndig-nd) - end - - 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) - 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 - str.chop! while str[-1]==zero[0] - str.chomp!(@dec_sep) - #puts str - end - - str << get_exp_char(neutral.base) - str << exp.to_s - - end - - end - end - - if @show_base && !@base_prefix - b_prefix = @base_indicators[neutral.base] - str << b_prefix if b_prefix - end - - - if @width>0 && @fill_char!='' - l = @width - str.length - if l>0 - case @adjust - when :internal - sign = '' - 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 - str = @fill_char*(l/2) + str + @fill_char*(l-l/2) - when :right - str = @fill_char*l + str - when :left - str = str + @fill_char*l - end - end - end - - return str - end - - # round a neutral numeral according to the format options - def round!(neutral) # :nodoc: - neutral.round! @ndig, @mode, @round - end - - @@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 - - 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 - - - opt = getRepDecOpt(base) - if @rep_in - #raise InvalidFormat,"Invalid numerical base" if base!=10 - rd = RepDec.new # get_base not necessary: setS sets it from options - rd.setS txt, opt - num = rd.to_NeutralNum(opt.digits) - else - # to do: use RepDec.parse; then build NeutralNum directly - opt.set_delim '','' - opt.set_suffix '' - rd = RepDec.new # get_base not necessary: setS sets it from options - rd.setS txt, opt - num = rd.to_NeutralNum(opt.digits) - end - num.rounding = get_round - num.dec_pos += exp - return num - end - - - @@fmts = { - :def=>Fmt.new.freeze - } - # Returns the current default format. - def self.default - d = self[:def] - if block_given? - d = d.dup - yield d - end - d - end - # Defines the current default format. - def self.default=(fmt) - self[:def] = fmt - end - # Assigns a format to a name in the formats repository. - def self.[]=(tag,fmt_def) - @@fmts[tag.to_sym]=fmt_def.freeze - 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, - :rounding_mode=>:round, - :approx_mode=>:approx, - :sci_digits=>:sci_format, - :non_signitificative_digits=>:non_sig, - :begin=>:rep_begin, - :end=>:rep_end, - :suffix=>:rep_auto, - :nreps=>:rep_n, - :read=>:rep_in - } - 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]=='.' ? ',' : '.' - 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 - 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? - properties[:all_digits] = true if properties[:non_sig]!='' - end - - if !properties[:base_radix].nil? || !properties[:base_uppercase].nil? - 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 - end - - def set(properties={}) # :nodoc: - self.dup.set!(properties) - end - - def use_scientific?(neutral,exp) # :nodoc: - 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 - end - end - - def getRepDecOpt(base=nil) # :nodoc: - rd_opt = RepDec::Opt.new - rd_opt.begin_rep = @rep_begin - rd_opt.end_rep = @rep_end - 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.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 - # end - return rd_opt - end - - def group(digits) # :nodoc: - RepDec.group_digits(digits, getRepDecOpt) - end - - end - - # This is a mix-in module to add formatting capabilities no numerical classes. - # A class that includes this module should provide the methods - # nio_write_neutral(fmt):: an instance method to write the value to - # a neutral numeral. The format is passed so that - # the base, for example, is available. - # nio_read_neutral(neutral):: a class method to create a value from a neutral - # numeral. - module Formattable - - # This is the method available in all formattable objects - # to format the value into a text string according - # to the optional format passed. - def nio_write(fmt=Fmt.default) - neutral = nio_write_neutral(fmt) - 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 - # 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 - 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 - 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('.',',') - Fmt[:dot_th] = Fmt.sep('.',',',[3]) - Fmt[:code] = Fmt.new.prec(20) # don't use :exact to avoid repeating numerals - - class Fmt - # Intermediate conversion format for simplified conversion - CONV_FMT = Fmt.prec(:exact).rep('<','>','...',0).approx_mode(:simplify) - # Intermediate conversion format for exact conversion - CONV_FMT_STRICT = Fmt.prec(:exact).rep('<','>','...',0).approx_mode(:exact) - # Numerical conversion: converts the quantity +x+ to an object - # of class +type+. - # - # The third parameter is the kind of conversion: - # [<tt>:approx</tt>] - # Tries to find an approximate simpler value if possible for inexact - # numeric types. This is the default. This is slower in general and - # may take some seconds in some cases. - # [<tt>:exact</tt>] - # Performs a conversion as exact as possible. - # The third parameter is true for approximate - # conversion (inexact values are simplified if possible) and false - # for conversions as exact as possible. - def Fmt.convert(x, type, mode=:approx) - fmt = mode==:approx ? CONV_FMT : CONV_FMT_STRICT - # return x.prec(type) - if !(x.is_a?(type)) - # return type.nio_read(x.nio_write(fmt),fmt) - - x = x.nio_write_neutral(fmt) - x = type.nio_read_neutral(x) - - end - x - end - end - - module_function - - def nio_float_to_bigdecimal(x,prec) # :nodoc: - if prec.nil? - x = Nio.convert(x,BigDecimal,:approx) - elsif prec==:exact - x = Nio.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) - x = nil - - honor_rounding = true - - if neutral.special? - case neutral.special - 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 - - 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 - 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) - else - x *= Float(neutral.base**k) - end - x = -x if neutral.sign=='-' - elsif !honor_rounding && (k>0 && (k+neutral.digits.length < 2*nd)) - j = k-neutral.digits.length - x = neutral.digits.to_i(neutral.base).to_f * Float(neutral.base**(j)) - x *= Float(neutral.base**(k-j)) - x = -x if neutral.sign=='-' - elsif neutral.base.modulo(Float::RADIX)==0 - - f = neutral.digits.to_i(neutral.base) - e = neutral.dec_pos-neutral.digits.length - - rounding = neutral.rounding - - 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=='-' - - else - - f = neutral.digits.to_i(neutral.base) - e = neutral.dec_pos-neutral.digits.length - - rounding = neutral.rounding - - 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=='-' - - 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 - - if x!=0 - q = x.nio_r(Nio::Tolerance.decimals(Float::DIG,:sig)) - 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 fmt.get_base==10 && false - txt = format "%.*e",Float::DECIMAL_DIG-1,x # note that spec. e output precision+1 significant digits - - sign = '+' - if txt[0,1]=='-' - sign = '-' - txt = txt[1...txt.length] - end - exp = 0 - x_char = fmt.get_exp_char(fmt.get_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 - - dec_pos = txt.index '.' - if dec_pos==nil - 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 - 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 - else - # normalized number - f = Math.ldexp(f,Float::MANT_DIG) - 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 - 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) - end - 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) - x = nil - - if neutral.special? - raise Nio::InvalidFormat,"Invalid integer numeral" - 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) - 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 - # 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 - 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) - x = nil - - if neutral.special? - case neutral.special - when :nan - x = Rational(0,0) - when :inf - 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 - neutral.set_special(:inf) - elsif x.numerator<0 - neutral.set_special(:inf,'-') - else - neutral.set_special(:nan) - end - else - if fmt.get_base==10 - rd = Nio::RepDec.new.setQ(x.numerator,x.denominator) - else - opt = Nio::RepDec::DEF_OPT.dup.set_digits(fmt.get_base_digits) - 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) - 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 - - x,y = neutral.to_RepDec.getQ - x = BigDecimal(x.to_s)/y - - else - if neutral.base==10 - #x = BigDecimal(neutral.digits) - #x *= BigDecimal("1E#{(neutral.dec_pos-neutral.digits.length)}") - #x = -x if neutral.sign=='-' - 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.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 - 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) - 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 - txt = x.to_s - - sign = '+' - if txt[0,1]=='-' - sign = '-' - txt = txt[1...txt.length] - end - exp = 0 - x_char = fmt.get_exp_char(fmt.get_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 - - dec_pos = txt.index '.' - if dec_pos==nil - 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 - 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) - - 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 - 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) - end - 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 - +# Formatting numbers as text + +# 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' + +require 'nio/repdec' + +require 'nio/rtnlzr' + +require 'rational' + +require 'bigdecimal' + +module Nio + + # positional notation, unformatted numeric literal: used as intermediate form + 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 + attr_writer :sign, :digits, :dec_pos, :rep_pos, :special, :inexact, :rounding + + # set number + def set(s,d,p=nil,r=nil,dgs=DigitsDef.base(10),inexact=false,rounding=:inf,normalize=true) + @sign = s # sign: '+','-','' + @digits = d # digits string + @dec_pos = p==nil ? d.length : p # position of decimal point: 0=before first digit... + @rep_pos = r==nil ? d.length : r # first repeated digit (0=first digit...) + @dgs = dgs + @base = @dgs.radix + @inexact = inexact + @special = nil + @rounding = rounding + trimZeros unless inexact + self + end + # set infinite (:inf) and invalid (:nan) numbers + def set_special(s,sgn='') # :inf, :nan + @special = s + @sign = sgn + self + end + + def base + @base + end + def base_digits + @dgs + end + def base_digits=(dd) + @dgs = dd + @base = @dgs.radix + end + def base=(b) + @dgs = DigitsDef.base(b) + @base=@dgs.radix + end + + # check for special numbers (which have only special and sign attributes) + def special? + special != nil + end + + # check for special numbers (which have only special and sign attributes) + def inexact? + @inexact + end + + def dup + n = NeutralNum.new + if special? + n.set_special @special.dup, @sign.dup + 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 + end + + def zero? + z = false + if !special + if digits=='' + z = true + else + z = true + for i in (0...@digits.length) + if dig_value(i)!=0 + z = false + break + end + end + end + end + z + end + + def round!(n, mode=:fix, dir=nil) + dir ||= rounding + trimLeadZeros + if n==:exact + return unless @inexact + n = @digits.size + end + + n += @dec_pos if mode==:fix + n = [n,@digits.size].min if @inexact + + adj = 0 + dv = :tie + if @inexact && n==@digits.size + dv = @inexact==:roundup ? :hi : :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 + end + end + + if dv==:hi + adj = +1 + elsif dv==:tie + if dir==:inf # towards nearest +/-infinity + 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 + # adj = +1 unless (dig_value(n-1)%2)!=0 + end + end + + if n>@digits.length + (@digits.length...n).each do |i| + @digits << dig_char(dig_value(i)) + @rep_pos += 1 + end + end + + prefix = '' + i = n-1 + while adj!=0 + v = dig_value(i) + v += adj + adj = 0 + if v<0 + v += @base + adj = -1 + elsif v>=@base + v -= @base + adj = +1 + end + if i<0 + prefix = dig_char(v)+prefix + elsif i<@digits.length + @digits[i] = dig_char(v) + end + i += -1 + end + + if n<0 + @digits = "" + else + @digits = @digits[0...n] + end + @rep_pos = @digits.length + + if prefix!='' + @digits = prefix + @digits + @dec_pos += prefix.length + @rep_pos += prefix.length + end + + + end + + def round(n, mode=:fix, dir=nil) + dir ||= rounding + nn = dup + nn.round!(n,mode,dir) + return nn + end + + def trimTrailZeros() + i = @digits.length + while i>0 && dig_value(i-1)==0 + i -= 1 + end + if @rep_pos>=i + @digits = @digits[0...i] + @rep_pos = i + end + + if @digits=='' + @digits = dig_char(0) # '0' + @rep_pos = 1 + @dec_pos = 1 + end + + end + + def trimLeadZeros() + i = 0 + while i<@digits.length && dig_value(i)==0 + i += 1 + end + @digits = @digits[i...@digits.length] + @dec_pos -= i + @rep_pos -= i + + if @digits=='' + @digits = dig_char(0) # '0' + @rep_pos = 1 + @dec_pos = 1 + end + + end + + def trimZeros() + trimLeadZeros + trimTrailZeros + end + + protected + + def dig_value(i) + v = 0 + if i>=@rep_pos + i -= @digits.length + i %= @digits.length - @rep_pos if @rep_pos<@digits.length + i += @rep_pos + 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 + #def digcode_value(c) + # v = c-?0 + # if v>9 + # v = 10 + c.chr.downcase[0] - ?a + # end + # v + # @dgs.digit_value(c) + #end + + def dig_char(v) + c = '' + if v!=nil && v>=0 && v<@base + c = @dgs.digit_char(v).chr + end + c + end + + end + + class NeutralNum + public + def to_RepDec + n = RepDec.new(@base) + if special? + + case special + when :nan + n.ip = :indeterminate + when :inf + if sign=='-' + n.ip = :posinfinity + else + n.ip :neginfinity + end + else + n = nil + end + + 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 + 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 + i=0 if i>=digits.length-rep_pos + end + n.d = [] + while i<digits.length-rep_pos + n.d << @dgs.digit_value(digits[rep_pos+i]) + i += 1 + end + new_rep_pos = n.d.size + dec_pos + n.d += text_to_digits(digits[rep_pos..-1]) + self.rep_pos = new_rep_pos + else + n.ip *= @base**(dec_pos-digits.length) + 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 + 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 + n.sign = -1 if sign=='-' + 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)} + ds = [] + txt.each_byte{|b| ds << @dgs.digit_value(b)} + ds + end + end + + class RepDec + public + def to_NeutralNum(base_dgs=nil) + num = NeutralNum.new + if !ip.is_a?(Integer) + + case ip + when :indeterminate + num.set_special :nan + when :posinfinity + num.set_special :inf,'+' + 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) + dec_pos = decimals.length + 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 + + # A Fmt object defines a numeric format. + # + # The formatting aspects managed by Fmt are: + # * mode and precision + # - #mode() and #orec() set the main paramters + # - see also #show_all_digits(), #approx_mode(), #insignificant_digits(), + # #sci_digits(), #show_exp_plus() and #show_plus() + # * separators + # - see #sep() and #grouping() + # * field justfification + # - #width() and the shortcut #pad0s() + # * numerical base + # - #base() + # * 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. + # + # This class also contains class methods for numeric conversion: + # * Fmt.convert + # and for default and other predefined formats: + # * Fmt.default / Fmt.default= + # * Fmt.[] / Fmt.[]= + # + # The actual formatted reading and writting if performed by + # * #nio_write() (Nio::Formattable#nio_write) + # * #nio_read() (Nio::Formattable::ClassMethods#nio_read) + # Finally numerical objects can be rounded according to a format: + # * #nio_round() (Nio::Formattable#nio_round) + class Fmt + include StateEquivalent + + class Error < StandardError # :nodoc: + end + class InvalidOption < Error # :nodoc: + end + class InvalidFormat < Error # :nodoc: + end + + @@default_rounding_mode = :even + def initialize() + + @dec_sep = '.' + @grp_sep = ',' + @grp = [] + + @ndig = :exact + @mode=:gen + @round=Fmt.default_rounding_mode + @all_digits = false + @approx = :only_sig + @non_sig = '' # marker for insignificant digits of inexact values e.g. '#','0' + @sci_format = 1 # number of integral digits in the mantissa: -1 for all + + @show_plus = false + @show_exp_plus = false + + @plus_symbol = nil + @minus_symbol = nil + + @rep_begin = '<' + @rep_end = '>' + @rep_auto = '...' + @rep_n = 2 + @rep_in = true + + @width = 0 + @fill_char = ' ' + @adjust=:right + + @base_radix = 10 + @base_uppercase = true + @base_digits = DigitsDef.base(@base_radix, !@base_uppercase) + @show_base = false + @base_indicators = { 2=>'b', 8=>'o', 10=>'', 16=>'h', 0=>'r'} # 0: generic (used with radix) + @base_prefix = false + + @nan_txt = 'NAN' + @inf_txt = 'Infinity' + + yield self if block_given? + end + + # Defines the separators used in numerals. This is relevant to + # 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. + # + # Finally, the third argument is an array that defines the groups + # of digits to separate. + # By default it's [], which means that no grouping will be produced on output + # (but the group separator defined will be ignored in input.) + # To produce the common thousands separation a value of [3] must be passed, + # which means that groups of 3 digits are used. + def sep(dec_sep,grp_sep=nil,grp=nil) + dup.sep!(dec_sep,grp_sep,grp) + end + # This is the mutator version of #sep(). + def sep!(dec_sep,grp_sep=nil,grp=nil) + set! :dec_sep=>dec_sep, :grp_sep=>grp_sep, :grp=>grp + end + + # This defines the grouping of digits (which can also be defined in #sep() + def grouping(grp=[3],grp_sep=nil) + dup.grouping!(grp,grp_sep) + end + # 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 + # 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 + # and define the grouping as with #grouping(). + def Fmt.grouping(grp=[3],grp_sep=nil) + Fmt.default.grouping(grp,grp_sep) + end + + # Define the formatting mode. There are two fixed parameters: + # - <tt>mode</tt> (only relevant for output) + # [<tt>:gen</tt>] + # (general) chooses automatically the shortes format + # [<tt>:fix</tt>] + # (fixed precision) is a simple format with a fixed number of digits + # after the point + # [<tt>:sig</tt>] + # (significance precision) is like :fix but using significant digits + # [<tt>:sci</tt>] + # (scientific) is the exponential form 1.234E2 + # - <tt>precision</tt> (number of digits or :exact, only used for output) + # [<tt>exact</tt>] + # means that as many digits as necessary to unambiguosly define the + # value are used; this is the default. + # + # 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; + # 1.5 is rounded to 2, -1.5 to -2 + # [<tt>:zero</tt>] + # 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 + # [<tt>:only_sig</tt>] + # (the default) treats the value as an approximation and only + # significant digits (those that cannot take an arbitrary value without + # changing the specified value) are shown. + # [<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 + # 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={}) + dup.mode!(mode,precision,options) + end + # This is the mutator version of #mode(). + def mode!(mode,precision=nil,options={}) + set! options.merge(:mode=>mode, :ndig=>precision) + end + + # 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 + # 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 + # 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 + # and define the formatting mode as with #prec() + def Fmt.prec(ndig,mode=nil,options={}) + Fmt.default.prec(ndig,mode,options) + end + + # Rounding mode used when not specified otherwise + def Fmt.default_rounding_mode + @@default_rounding_mode + end + # The default rounding can be changed here; it starts with the value :even. + # See the rounding modes available in the description of method #mode(). + def Fmt.default_rounding_mode=(m) + @@default_rounding_mode=m + 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 + # This is the mutator version of #show_all_digits(). + def show_all_digits!(ad=true) + set! :all_digits=>ad + end + # This defines the approximate mode (:only_sig, :exact, :simplify) + # just like the last parameter of #mode() + def approx_mode(mode) + dup.approx_mode! mode + end + # This is the mutator version of #approx_mode(). + def approx_mode!(mode) + set! :approx=>mode + end + # Defines a character to stand for insignificant digits when + # a specific number of digits has been requested greater than then + # number of significant digits (for approximate types). + def insignificant_digits(ch='#') + dup.insignificant_digits! ch + end + # This is the mutator version of #insignificant_digits(). + def insignificant_digits!(ch='#') + ch ||= '' + set! :non_sig=>ch + end + # Defines the number of significan digits before the radix separator + # in scientific notation. A negative value will set all significant digits + # before the radix separator. The special value <tt>:eng</tt> activates + # _engineering_ mode, in which the exponents are multiples of 3. + # + # For example: + # 0.1234.nio_write(Fmt.mode(:sci,4).sci_digits(0) -> 0.1234E0 + # 0.1234.nio_write(Fmt.mode(:sci,4).sci_digits(3) -> 123.4E-3 + # 0.1234.nio_write(Fmt.mode(:sci,4).sci_digits(-1) -> 1234.E-4 + # 0.1234.nio_write(Fmt.mode(:sci,4).sci_digits(:eng) -> 123.4E-3 + def sci_digits(n=-1) + dup.sci_digits! n + end + # 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 + # 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 + # 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 + # 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 + # and define sci_digits + def Fmt.sci_digits(v=-1) + Fmt.default.sci_digits(v) + end + + # Controls the display of the sign for positive numbers + def show_plus(sp=true) + dup.show_plus! sp + end + # This is the mutator version of #show_plus(). + def show_plus!(sp=true) + set! :show_plus=>sp + set! :plus_symbol=>sp if sp.kind_of?(String) + self + end + + # Controls the display of the sign for positive exponents + def show_exp_plus(sp=true) + dup.show_exp_plus! sp + end + # This is the mutator version of #show_plus(). + def show_exp_plus!(sp=true) + 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 + # 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 + # and define show_exp_plus + def Fmt.show_exp_plus(v=true) + Fmt.default.show_exp_plus(v) + end + + # Defines the handling and notation for repeating numerals. The parameters + # can be passed in order or in a hash: + # [<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; + # 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) + + params << {} if params.size==0 + if params[0].kind_of?(Hash) + params = params[0] + 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 + # and define the repeating decimals mode as with #rep() + def Fmt.rep(*params) + Fmt.default.rep(*params) + end + + # Sets the justificaton width, mode and fill character + # + # The mode accepts these values: + # [<tt>:right</tt>] (the default) justifies to the right (adds padding at the left) + # [<tt>:left</tt>] justifies to the left (adds padding to the right) + # [<tt>:internal</tt>] like :right, but the sign is kept to the left, outside the padding. + # [<tt>:center</tt>] centers the number in the field + def width(w,adj=nil,ch=nil) + dup.width! w,adj,ch + end + # This is the mutator version of #width(). + def width!(w,adj=nil,ch=nil) + set! :width=>w, :adjust=>adj, :fill_char=>ch + end + # Defines the justification (as #width()) with the given + # width, internal mode and filling with zeros. + # + # Note that if you also use grouping separators, the filling 0s + # will not be separated. + def pad0s(w) + dup.pad0s! w + end + # This is the mutator version of #pad0s(). + def pad0s!(w) + width! w, :internal, '0' + end + # This is a shortcut to create a new Fmt object and define the width + # parameters as with #widht() + def Fmt.width(w,adj=nil,ch=nil) + Fmt.default.width(w,adj,ch) + end + # This is a shortcut to create a new Fmt object and define numeric + # padding as with #pad0s() + def Fmt.pad0s(w) + Fmt.default.pad0s(w) + end + + # defines the numerical base; the second parameters forces the use + # of uppercase letters for bases greater than 10. + def base(b, uppercase=nil) + dup.base! b, uppercase + end + # This is the mutator version of #base(). + def base!(b, uppercase=nil) + set! :base_radix=>b, :base_uppercase=>uppercase + end + # This is a shortcut to create a new Fmt object and define the base + def Fmt.base(b, uppercase=nil) + Fmt.default.base(b, uppercase) + end + # returns the exponent char used with the specified base + def get_exp_char(base) # :nodoc: + base ||= @base_radix + base<=10 ? 'E' : '^' + end + + # returns the base + def get_base # :nodoc: + @base_radix + end + # returns the digit characters used for a base + def get_base_digits(b=nil) # :nodoc: + (b.nil? || b==@base_radix) ? @base_digits : DigitsDef.base(b,!@base_uppercase) + end + # returns true if uppercase digits are used + def get_base_uppercase? # :nodoc: + @base_uppercase + end + + # returns the formatting mode + def get_mode # :nodoc: + @mode + end + # returns the precision (number of digits) + def get_ndig # :nodoc: + @ndig + end + # return the show_all_digits state + def get_all_digits? # :nodoc: + @all_digits + end + # returns the approximate mode + def get_approx # :nodoc: + @approx + end + + # returns the rounding mode + def get_round # :nodoc: + @round + end + + # Method used internally to format a neutral numeral + def nio_write_formatted(neutral) # :nodoc: + str = '' + if neutral.special? + str << neutral.sign + case neutral.special + when :inf + str << @inf_txt + when :nan + str << @nan_txt + end + else + 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 + if @ndig.kind_of?(Numeric) && @ndig>0 && @mode==:fix + str << @dec_sep << zero*@ndig + end + else + + neutral.trimLeadZeros + actual_mode = @mode + trim_trail_zeros = !@all_digits # false + + integral_digits = @sci_format + if integral_digits == :eng + integral_digits = 1 + while (neutral.dec_pos - integral_digits).modulo(3) != 0 + integral_digits += 1 + end + elsif integral_digits==:all || integral_digits < 0 + if neutral.inexact? && @non_sig!='' && @ndig.kind_of?(Numeric) + integral_digits = @ndig + 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 + when :fix, :sig #, :gen + + + if @show_plus || neutral.sign!='+' + str << ({'-'=>@minus_symbol, '+'=>@plus_symbol}[neutral.sign] || neutral.sign) + end + + + + if @show_base && @base_prefix + b_prefix = @base_indicators[neutral.base] + str << b_prefix if b_prefix + end + + if @ndig==:exact + neutral.sign = '+' + str << neutral.to_RepDec.getS(@rep_n, getRepDecOpt(neutral.base)) + else + #zero = get_base_digits.digit_char(0).chr + ns_digits = '' + + nd = neutral.digits.length + if actual_mode==:fix + nd -= neutral.dec_pos + end + if neutral.inexact? && @ndig>nd # assert no rep-dec. + ns_digits = @non_sig*(@ndig-nd) + 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 + 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 + + #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 + 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) + end + + + if @show_base && @base_prefix + b_prefix = @base_indicators[neutral.base] + str << b_prefix if b_prefix + end + + #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)) + else + ns_digits = '' + + nd = neutral.digits.length + if actual_mode==:fix + nd -= neutral.dec_pos + end + if neutral.inexact? && @ndig>nd # assert no rep-dec. + ns_digits = @non_sig*(@ndig-nd) + end + + 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) + 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 + str.chop! while str[-1]==zero[0] + str.chomp!(@dec_sep) + #puts str + end + + 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 + + if @show_base && !@base_prefix + b_prefix = @base_indicators[neutral.base] + str << b_prefix if b_prefix + end + + + if @width>0 && @fill_char!='' + l = @width - str.length + if l>0 + case @adjust + when :internal + sign = '' + 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 + str = @fill_char*(l/2) + str + @fill_char*(l-l/2) + when :right + str = @fill_char*l + str + when :left + str = str + @fill_char*l + end + end + end + + return str + end + + # round a neutral numeral according to the format options + def round!(neutral) # :nodoc: + neutral.round! @ndig, @mode, @round + end + + @@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 + + 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 + + + opt = getRepDecOpt(base) + if @rep_in + #raise InvalidFormat,"Invalid numerical base" if base!=10 + rd = RepDec.new # get_base not necessary: setS sets it from options + rd.setS txt, opt + num = rd.to_NeutralNum(opt.digits) + else + # to do: use RepDec.parse; then build NeutralNum directly + opt.set_delim '','' + opt.set_suffix '' + rd = RepDec.new # get_base not necessary: setS sets it from options + rd.setS txt, opt + num = rd.to_NeutralNum(opt.digits) + end + num.rounding = get_round + num.dec_pos += exp + return num + end + + + @@fmts = { + :def=>Fmt.new.freeze + } + # Returns the current default format. + def self.default + d = self[:def] + if block_given? + d = d.dup + yield d + end + d + end + # Defines the current default format. + def self.default=(fmt) + self[:def] = fmt + end + # Assigns a format to a name in the formats repository. + def self.[]=(tag,fmt_def) + @@fmts[tag.to_sym]=fmt_def.freeze + 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, + :rounding_mode=>:round, + :approx_mode=>:approx, + :sci_digits=>:sci_format, + :non_signitificative_digits=>:non_sig, + :begin=>:rep_begin, + :end=>:rep_end, + :suffix=>:rep_auto, + :nreps=>:rep_n, + :read=>:rep_in + } + 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]=='.' ? ',' : '.' + 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 + 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? + properties[:all_digits] = true if properties[:non_sig]!='' + end + + if !properties[:base_radix].nil? || !properties[:base_uppercase].nil? + 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 + end + + def set(properties={}) # :nodoc: + self.dup.set!(properties) + end + + def use_scientific?(neutral,exp) # :nodoc: + 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 + end + end + + def getRepDecOpt(base=nil) # :nodoc: + rd_opt = RepDec::Opt.new + rd_opt.begin_rep = @rep_begin + rd_opt.end_rep = @rep_end + 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.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 + # end + return rd_opt + end + + def group(digits) # :nodoc: + RepDec.group_digits(digits, getRepDecOpt) + end + + end + + # This is a mix-in module to add formatting capabilities no numerical classes. + # A class that includes this module should provide the methods + # nio_write_neutral(fmt):: an instance method to write the value to + # a neutral numeral. The format is passed so that + # the base, for example, is available. + # nio_read_neutral(neutral):: a class method to create a value from a neutral + # numeral. + module Formattable + + # This is the method available in all formattable objects + # to format the value into a text string according + # to the optional format passed. + def nio_write(fmt=Fmt.default) + neutral = nio_write_neutral(fmt) + 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 + # 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 + 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 + 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('.',',') + Fmt[:dot_th] = Fmt.sep('.',',',[3]) + Fmt[:code] = Fmt.new.prec(20) # don't use :exact to avoid repeating numerals + + class Fmt + # Intermediate conversion format for simplified conversion + CONV_FMT = Fmt.prec(:exact).rep('<','>','...',0).approx_mode(:simplify) + # Intermediate conversion format for exact conversion + CONV_FMT_STRICT = Fmt.prec(:exact).rep('<','>','...',0).approx_mode(:exact) + # Numerical conversion: converts the quantity +x+ to an object + # of class +type+. + # + # The third parameter is the kind of conversion: + # [<tt>:approx</tt>] + # Tries to find an approximate simpler value if possible for inexact + # numeric types. This is the default. This is slower in general and + # may take some seconds in some cases. + # [<tt>:exact</tt>] + # Performs a conversion as exact as possible. + # The third parameter is true for approximate + # conversion (inexact values are simplified if possible) and false + # for conversions as exact as possible. + def Fmt.convert(x, type, mode=:approx) + fmt = mode==:approx ? CONV_FMT : CONV_FMT_STRICT + # return x.prec(type) + if !(x.is_a?(type)) + # return type.nio_read(x.nio_write(fmt),fmt) + + x = x.nio_write_neutral(fmt) + x = type.nio_read_neutral(x) + + end + x + end + end + + module_function + + def nio_float_to_bigdecimal(x,prec) # :nodoc: + if prec.nil? + x = Fmt.convert(x,BigDecimal,:approx) + elsif prec==: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) + x = nil + + honor_rounding = true + + if neutral.special? + case neutral.special + 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 + + 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 + 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) + else + x *= Float(neutral.base**k) + end + x = -x if neutral.sign=='-' + elsif !honor_rounding && (k>0 && (k+neutral.digits.length < 2*nd)) + j = k-neutral.digits.length + x = neutral.digits.to_i(neutral.base).to_f * Float(neutral.base**(j)) + x *= Float(neutral.base**(k-j)) + x = -x if neutral.sign=='-' + elsif neutral.base.modulo(Float::RADIX)==0 + + f = neutral.digits.to_i(neutral.base) + e = neutral.dec_pos-neutral.digits.length + + rounding = neutral.rounding + + 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=='-' + + else + + f = neutral.digits.to_i(neutral.base) + e = neutral.dec_pos-neutral.digits.length + + rounding = neutral.rounding + + 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=='-' + + 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 + + if x!=0 + q = x.nio_r(Nio::Tolerance.decimals(Float::DIG,:sig)) + 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 fmt.get_base==10 && false + txt = format "%.*e",Float::DECIMAL_DIG-1,x # note that spec. e output precision+1 significant digits + + sign = '+' + if txt[0,1]=='-' + sign = '-' + txt = txt[1...txt.length] + end + exp = 0 + x_char = fmt.get_exp_char(fmt.get_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 + + dec_pos = txt.index '.' + if dec_pos==nil + 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 + 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 + else + # normalized number + f = Math.ldexp(f,Float::MANT_DIG) + 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 + 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) + end + 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) + x = nil + + if neutral.special? + raise Nio::InvalidFormat,"Invalid integer numeral" + 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) + 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 + # 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 + 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) + x = nil + + if neutral.special? + case neutral.special + when :nan + x = Rational(0,0) + when :inf + 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 + neutral.set_special(:inf) + elsif x.numerator<0 + neutral.set_special(:inf,'-') + else + neutral.set_special(:nan) + end + else + if fmt.get_base==10 + rd = Nio::RepDec.new.setQ(x.numerator,x.denominator) + else + opt = Nio::RepDec::DEF_OPT.dup.set_digits(fmt.get_base_digits) + 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) + 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 + + x,y = neutral.to_RepDec.getQ + x = BigDecimal(x.to_s)/y + + else + if neutral.base==10 + #x = BigDecimal(neutral.digits) + #x *= BigDecimal("1E#{(neutral.dec_pos-neutral.digits.length)}") + #x = -x if neutral.sign=='-' + 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.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 + 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) + 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 + txt = x.to_s + + sign = '+' + if txt[0,1]=='-' + sign = '-' + txt = txt[1...txt.length] + end + exp = 0 + x_char = fmt.get_exp_char(fmt.get_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 + + dec_pos = txt.index '.' + if dec_pos==nil + 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 + 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) + + 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 + 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) + end + 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 +