class Numeric

Addes Numeirc class to {#strfnum} method.

Public Instance Methods

strfnum(format) click to toggle source
Converts the numeric value of this instance to the string representation,
using the numeric format strings.

Numeric Format Strings

Uses the following characters as numeric format strings.
[<tt>"0"</tt> (Zero Placeholder)] Replaces <tt>"0"</tt> with the
                                  corresponding digit if one is present;
                                  otherwize, zero appears.
[<tt>"#"</tt> (Digit Placeholder)] Replaces <tt>"#"</tt> with the
                                   corresponding digit if one is present;
                                   otherwise, no digit appears.
[<tt>"."</tt> (Decimal Point)] Determines the location of the decimal
                               separator.
[<tt>","</tt> (Group Separator)] Serves as both a group separator if _zero_
                                 or _sharp_ appears between _comma_ and
                                 _dot_ or format strings does not include
                                 _dot_; otherwise, serves as number scaling
                                 specifier. If format strings includes a
                                 group separator, it inserts a group
                                 separater character between each group.
                                 Including a number scaling specifier, it
                                 divides a number by 1000 (it depends on
                                 group size) for each _comma_ specified.
[<tt>"%"</tt> (Percentage Placeholder)] Multiplies a number by 100 and
                                        inserts a percentage symbol.
[<tt>"\"</tt> (Escape Character)] Causes the next character to be
                                  interpreted as a literal.
[<tt>";"</tt> (Section Separater)] Defines sections with separate format
                                   strings for positive, negative, and zero
                                   numbers.
[Other Characters] The character is copied to the result string unchanged.

  3.141592.strfnum('0.00')           # => "3.14"
  3.141592.strfnum('0.###')          # => "3.142"
  3.140159.strfnum('0.###')          # => "3.14"
  3.140159.strfnum('000.00')         # => "003.14"
  1234567.strfnum('#,##0')           # => "1,234,567"
  1234567.strfnum('#,##0,.0')        # => "1,234.6"
  0.56789.strfnum('0.0%')            # => "56.8%"
  -12345.strfnum('#,##0')            # => "-12,345"
  -12345.strfnum('#,##0;*#,##0')     # => "*12,345"
  0.001.strfnum('#,##0;-#,##0;zero') # => "zero"
  12345.strfnum('\##,##0')           # => "#12,345"

Customizes Separater Character

Defining following constants, customizes the separator, scaling group size
or the percent symbol.
[+Numeric::DECIMAL_SEPARATOR+] (+String+) A decimal separator. If undefined,
                               uses <tt>"."</tt>.
[+Numeric::GROUP_SEPARATOR+] (+String+) A number scaling separator. If
                             undefined, uses <tt>","</tt>.
[+Numeric::GROUP_SIZES+] (+Integer+) Numeric scaling group size. If
                         undefined, uses 3.
[+Numeric::PERCENT_SYMBOL+] (+String+) A percentage symbol. If undefined,
                            uses <tt>"%"</tt>.

  Numeric::DECIMAL_SEPARATOR = ','
  Numeric::GROUP_SEPARATOR = ' '

  1234567.89.strfnum('#,##0.0')      # => "1 234 567,9"

@param [String] format the numeric format string
@return [String] formated string that is equivalent to this instance
# File lib/util.rb, line 87
def strfnum(format)
  decimal_separator = (defined? ::Numeric::DECIMAL_SEPARATOR) ? ::Numeric::DECIMAL_SEPARATOR : '.'
  group_separator = (defined? ::Numeric::GROUP_SEPARATOR) ? ::Numeric::GROUP_SEPARATOR : ','
  group_sizes = (defined? ::Numeric::GROUP_SIZES) ? ::Numeric::GROUP_SIZES : 3
  percent_symbol = (defined? ::Numeric::PERCENT_SYMBOL) ? ::Numeric::PERCENT_SYMBOL : '%'

  # lexical analysis
  sec = {:int => [], :dec => [], :int_digit => 0, :dec_digit => 0, :place => 0}
  sections = [sec]
  escaped = false
  forcibly = false
  format.split(//).each do |char|
    if escaped
      if sec[:decimal]
        sec[:dec].push(char)
      else
        sec[:int].push(char)
      end
      escaped = false
      next
    end

    case char
    when '0'
      if sec[:decimal]
        sec[:dec_digit] += 1
        sec[:dec].push(:zero)
      else
        forcibly = true
        sec[:int_digit] += 1
        sec[:int].push(:zero)
        if sec[:place] != 0
          sec[:use_separater] = true
          sec[:place] = 0
        end
      end
    when '#'
      if sec[:decimal]
        sec[:dec_digit] += 1
        sec[:dec].push(:sharp)
      else
        sec[:int_digit] += 1
        sec[:int].push(forcibly ? :zero : :sharp)
        if sec[:place] != 0
          sec[:use_separater] = true
          sec[:place] = 0
        end
      end
    when '.'
      sec[:decimal] = true
    when ','
      sec[:place] += group_sizes unless sec[:decimal]
    when ';'
      forcibly = false
      sec = {:int => [], :dec => [], :int_digit => 0, :dec_digit => 0, :place => 0}
      sections.push(sec)
    when '%'
      sec[:percent] = true
      if sec[:decimal]
        sec[:dec].push(percent_symbol)
      else
        sec[:int].push(percent_symbol)
      end
    when '\\'
      escaped = true
    else
      if sec[:decimal]
        sec[:dec].push(char)
      else
        sec[:int].push(char)
      end
    end
  end

  # choosing of the format (fmt)
  need_minus_sign = false
  case sections.size
  when 1
    need_minus_sign = (self < 0)
    frm = sections[0]
  when 2
    if self >= 0
      frm = sections[0]
    else
      frm = sections[1]
    end
  else
    if self > 0
      frm = sections[0]
    elsif self < 0
      frm = sections[1]
    else
      frm = sections[2]
    end
  end

  value = self

  value *= 100 if frm[:percent]
  value = value.quo(10 ** frm[:place]) if frm[:place] != 0

  int_part, dec_part = ("%.#{frm[:dec_digit]}f" % value.abs).split('.')

  if self.nonzero? && int_part == '0' && !(dec_part =~ /[^0]/)
    if sections[2]
      frm = sections[2]
      value = 0
      int_part, dec_part = ("%.#{frm[:dec_digit]}f" % value.abs).split('.')
    elsif self < 0 && sections[1]
      frm = sections[0]
      value = 0
      int_part, dec_part = ("%.#{frm[:dec_digit]}f" % value.abs).split('.')
    end
  end

  # formatting of integer part
  first = 0
  last = int_part.size - frm[:int_digit]
  int_index = frm[:int_digit]
  last_num = nil
  frm[:int].each_with_index do |place_holder, index|
    case place_holder
    when :zero
      if frm[:use_separater]
        if first == last || last < 0
          num = last < 0 ? '0' : int_part[first, 1]
          if last_num && int_index % group_sizes == 0
            frm[:int][index] = group_separator + num
          elsif last_num.nil? && need_minus_sign
            frm[:int][index] = '-' + num
          else
            frm[:int][index] = num
          end
        else
          num = int_part[first..last]
          i = (int_part.size - 1) % 3 + 1
          while i < num.size
            num.insert(i, group_separator)
            i += 4
          end
          frm[:int][index] = last_num.nil? && need_minus_sign ? '-' + num : num
        end
      else
        num = last < 0 ? '0' : int_part[first..last]
        num = '-' + num if last_num.nil? && need_minus_sign
        frm[:int][index] = num
      end
      first = (last += 1)
      int_index -= 1
      last_num = frm[:int][index]
    when :sharp
      if last < 0
        frm[:int][index] = nil
      elsif frm[:use_separater]
        if first == last
          if last_num && int_index % group_sizes == 0
            frm[:int][index] = group_separator + int_part[first, 1]
          elsif last_num.nil? && need_minus_sign
            frm[:int][index] = '-' + int_part[first, 1]
          else
            frm[:int][index] = int_part[first, 1]
          end
        else
          num = int_part[first..last]
          i = (int_part.size - 1) % 3 + 1
          while i < num.size
            num.insert(i, group_separator)
            i += 4
          end
          frm[:int][index] = last_num.nil? && need_minus_sign ? '-' + num : num
        end
      else
        num = int_part[first..last]
        num = '-' + num if last_num.nil? && need_minus_sign
        frm[:int][index] = num
      end
      first = (last += 1)
      int_index -= 1
      last_num = frm[:int][index]
    end
  end

  # formatting of decimal part
  needs_replaceing = false
  needs_decimal_separator = nil
  dec_index = frm[:dec_digit]
  (frm[:dec].size - 1).downto(0) do |index|
    case frm[:dec][index]
    when :zero
      dec_index -= 1
      frm[:dec][index] = dec_part[dec_index, 1]
      needs_replaceing = true
      needs_decimal_separator = decimal_separator
    when :sharp
      dec_index -= 1
      if needs_replaceing || dec_part[dec_index, 1] != '0'
        frm[:dec][index] = dec_part[dec_index, 1]
        needs_replaceing = true
        needs_decimal_separator = decimal_separator
      else
        frm[:dec][index] = nil
      end
    end
  end

  (frm[:int] + [needs_decimal_separator] + frm[:dec]).join
end