twitter/twitter-cldr-rb

View on GitHub
lib/twitter_cldr/formatters/numbers/number_formatter.rb

Summary

Maintainability
A
1 hr
Test Coverage
# encoding: UTF-8

# Copyright 2012 Twitter, Inc
# http://www.apache.org/licenses/LICENSE-2.0

module TwitterCldr
  module Formatters
    class NumberFormatter < Formatter

      attr_reader :data_reader

      def initialize(data_reader)
        @data_reader = data_reader
      end

      def format(tokens, number, options = {})
        options[:precision] ||= precision_from(number)
        options[:type] ||= :decimal

        prefix, suffix, integer_format, fraction_format = *partition_tokens(tokens)
        number = truncate_number(number, integer_format.format.length)

        int, fraction = parse_number(number, options)
        result =  integer_format.apply(int, options)
        result << fraction_format.apply(fraction, options) if fraction

        number_system.transliterate(
          "#{prefix.to_s}#{result}#{suffix.to_s}"
        )
      end

      def truncate_number(number, decimal_digits)
        if abbreviate?(number)
          factor = [0, number.to_i.abs.to_s.length - decimal_digits].max
          number / (10.0 ** factor)
        else
          number
        end
      end

      protected

      def number_system
        @number_system ||= TwitterCldr::Shared::NumberingSystem.for_name(
          data_reader.number_system
        )
      end

      def partition_tokens(tokens)
        [
          token_val_from(tokens[0]),
          token_val_from(tokens[2]),
          Numbers::Integer.new(
            tokens[1],
            data_reader.symbols
          ),
          Numbers::Fraction.new(
            tokens[1],
            data_reader.symbols
          )
        ]
      end

      def token_val_from(token)
        token ? token.value : ""
      end

      def parse_number(number, options = {})
        precision = options[:precision] || precision_from(number)
        rounding = options[:rounding] || 0

        if number.is_a? BigDecimal
          number = precision == 0 ?
            round_to(number, precision, rounding).abs.fix.to_s("F") :
            round_to(number, precision, rounding).abs.round(precision).to_s("F")
        else
          number = "%.#{precision}f" % round_to(number, precision, rounding).abs
        end
        number.split(".")
      end

      def round_to(number, precision, rounding = 0)
        factor = 10 ** precision
        result = number.is_a?(BigDecimal) ?
          ((number * factor).fix / factor) :
          ((number * factor).round.to_f / factor)

        if rounding > 0
          rounding = rounding.to_f / factor
          result = number.is_a?(BigDecimal) ?
            ((result *  (1.0 / rounding)).fix / (1.0 / rounding)) :
            ((result *  (1.0 / rounding)).round.to_f / (1.0 / rounding))
        end

        result
      end

      def precision_from(num)
        return 0 if num.is_a?(BigDecimal) && num.fix == num
        parts = (num.is_a?(BigDecimal) ? num.to_s("F") : num.to_s ).split(".")
        parts.size == 2 ? parts[1].size : 0
      end

      def abbreviate?(number)
        TwitterCldr::DataReaders::NumberDataReader.within_abbreviation_range?(number) && (
          data_reader.format == :short ||
          data_reader.format == :long
        )
      end

    end
  end
end