leandro/cardinality-br

View on GitHub
lib/brazilian_cardinality/number.rb

Summary

Maintainability
A
0 mins
Test Coverage
module BrazilianCardinality
  module Number
    NumberTooBigError = Class.new(StandardError)

    ONES = {
      0 => 'zero',
      1 => 'um',
      2 => 'dois',
      3 => 'três',
      4 => 'quatro',
      5 => 'cinco',
      6 => 'seis',
      7 => 'sete',
      8 => 'oito',
      9 => 'nove'
    }.freeze

    TENS = {
      10 => 'dez',
      11 => 'onze',
      12 => 'doze',
      13 => 'treze',
      14 => 'quatorze',
      15 => 'quinze',
      16 => 'dezesseis',
      17 => 'dezessete',
      18 => 'dezoito',
      19 => 'dezenove',
      20 => 'vinte',
      30 => 'trinta',
      40 => 'quarenta',
      50 => 'cinquenta',
      60 => 'sessenta',
      70 => 'setenta',
      80 => 'oitenta',
      90 => 'noventa'
    }.freeze

    HUNDREDS = {
      100 => 'cento',
      200 => 'duzentos',
      300 => 'trezentos',
      400 => 'quatrocentos',
      500 => 'quinhentos',
      600 => 'seiscentos',
      700 => 'setecentos',
      800 => 'oitocentos',
      900 => 'novecentos'
    }.freeze

    class << self
      def number_cardinal(number)
        negative = number.negative? ? 'menos ' : ''
        n = number.to_i.abs

        expression = case n
                     when 0..999 then cardinal_for_0_up_to_999(n)
                     when 1000..999_999_999_999_999 then cardinal_for_thousands_to_trillions(n)
                     else raise NumberTooBigError, "#{n} is too big"
                     end

        "#{negative}#{expression}"
      end

      private

      def cardinal_for_0_up_to_999(number)
        case number
        when 0..9 then ONES[number]
        when 10..19 then TENS[number]
        when 20..99 then cardinal_for_tens_and_hundreds(number, 10)
        when 100 then 'cem'
        when 101..999 then cardinal_for_tens_and_hundreds(number, 100)
        end
      end

      def cardinal_for_thousands_to_trillions(number)
        case number
        when 1000..999_999
          cardinal_for_scale_of_thousands(number, 1_000, 'mil', 'mil')
        when 1_000_000..999_999_999
          cardinal_for_scale_of_thousands(number, 1_000_000, 'milhão', 'milhões')
        when 1_000_000_000..999_999_999_999
          cardinal_for_scale_of_thousands(number, 1_000_000_000, 'bilhão', 'bilhões')
        when 1_000_000_000_000..999_999_999_999_999
          cardinal_for_scale_of_thousands(number, 1_000_000_000_000, 'trilhão', 'trilhões')
        end
      end

      def cardinal_for_tens_and_hundreds(number, scale)
        remainder = number % scale
        words_map = scale == 10 ? TENS : HUNDREDS
        return words_map[number] if remainder.zero?
        "#{words_map[number - remainder]} e #{number_cardinal(remainder)}"
      end

      def cardinal_for_scale_of_thousands(number, scale, singular, plural)
        quocient = number / scale
        remainder = number % scale
        word = quocient > 1 ? plural : singular
        high_order_units = "#{number_cardinal(quocient)} #{word}"
        return high_order_units if remainder.zero?
        "#{high_order_units} e #{number_cardinal(remainder)}"
      end
    end
  end
end