From d3b93e403b3d0c11607e037770ad91c062b2e897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Mon, 2 Dec 2013 22:12:36 -0200 Subject: Make load of NumberHelper thread safe --- activesupport/lib/active_support.rb | 7 +++ activesupport/lib/active_support/number_helper.rb | 18 +++--- .../number_helper/number_to_currency.rb | 51 ---------------- .../number_helper/number_to_currency_converter.rb | 48 +++++++++++++++ .../number_helper/number_to_delimited.rb | 25 -------- .../number_helper/number_to_delimited_converter.rb | 23 +++++++ .../number_helper/number_to_human.rb | 71 ---------------------- .../number_helper/number_to_human_converter.rb | 68 +++++++++++++++++++++ .../number_helper/number_to_human_size.rb | 63 ------------------- .../number_to_human_size_converter.rb | 60 ++++++++++++++++++ .../number_helper/number_to_percentage.rb | 17 ------ .../number_to_percentage_converter.rb | 14 +++++ .../number_helper/number_to_phone.rb | 54 ---------------- .../number_helper/number_to_phone_converter.rb | 51 ++++++++++++++++ .../number_helper/number_to_rounded.rb | 62 ------------------- .../number_helper/number_to_rounded_converter.rb | 60 ++++++++++++++++++ 16 files changed, 342 insertions(+), 350 deletions(-) delete mode 100644 activesupport/lib/active_support/number_helper/number_to_currency.rb create mode 100644 activesupport/lib/active_support/number_helper/number_to_currency_converter.rb delete mode 100644 activesupport/lib/active_support/number_helper/number_to_delimited.rb create mode 100644 activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb delete mode 100644 activesupport/lib/active_support/number_helper/number_to_human.rb create mode 100644 activesupport/lib/active_support/number_helper/number_to_human_converter.rb delete mode 100644 activesupport/lib/active_support/number_helper/number_to_human_size.rb create mode 100644 activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb delete mode 100644 activesupport/lib/active_support/number_helper/number_to_percentage.rb create mode 100644 activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb delete mode 100644 activesupport/lib/active_support/number_helper/number_to_phone.rb create mode 100644 activesupport/lib/active_support/number_helper/number_to_phone_converter.rb delete mode 100644 activesupport/lib/active_support/number_helper/number_to_rounded.rb create mode 100644 activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb (limited to 'activesupport/lib') diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 5e1fe9e556..a40c6b559c 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -52,6 +52,7 @@ module ActiveSupport autoload :MessageEncryptor autoload :MessageVerifier autoload :Multibyte + autoload :NumberHelper autoload :OptionMerger autoload :OrderedHash autoload :OrderedOptions @@ -63,6 +64,12 @@ module ActiveSupport autoload :Rescuable autoload :SafeBuffer, "active_support/core_ext/string/output_safety" autoload :TestCase + + def self.eager_load! + super + + NumberHelper.eager_load! + end end autoload :I18n, "active_support/i18n" diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb index 53f08efae5..2df1aada50 100644 --- a/activesupport/lib/active_support/number_helper.rb +++ b/activesupport/lib/active_support/number_helper.rb @@ -1,14 +1,18 @@ module ActiveSupport module NumberHelper + extend ActiveSupport::Autoload - autoload :NumberToRoundedConverter, "active_support/number_helper/number_to_rounded" - autoload :NumberToDelimitedConverter, "active_support/number_helper/number_to_delimited" - autoload :NumberToHumanConverter, "active_support/number_helper/number_to_human" - autoload :NumberToHumanSizeConverter, "active_support/number_helper/number_to_human_size" - autoload :NumberToPhoneConverter, "active_support/number_helper/number_to_phone" - autoload :NumberToCurrencyConverter, "active_support/number_helper/number_to_currency" - autoload :NumberToPercentageConverter, "active_support/number_helper/number_to_percentage" + eager_autoload do + autoload :NumberConverter + autoload :NumberToRoundedConverter + autoload :NumberToDelimitedConverter + autoload :NumberToHumanConverter + autoload :NumberToHumanSizeConverter + autoload :NumberToPhoneConverter + autoload :NumberToCurrencyConverter + autoload :NumberToPercentageConverter + end extend self diff --git a/activesupport/lib/active_support/number_helper/number_to_currency.rb b/activesupport/lib/active_support/number_helper/number_to_currency.rb deleted file mode 100644 index 402b0b56aa..0000000000 --- a/activesupport/lib/active_support/number_helper/number_to_currency.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'active_support/number_helper/number_converter' -require 'active_support/number_helper/number_to_rounded' - -module ActiveSupport - module NumberHelper - class NumberToCurrencyConverter < NumberConverter # :nodoc: - - self.namespace = :currency - - def convert - number = @number.to_s.strip - format = options[:format] - - if is_negative?(number) - format = options[:negative_format] - number = absolute_value(number) - end - - rounded_number = NumberToRoundedConverter.new(number, options).execute - format.gsub('%n', rounded_number).gsub('%u', options[:unit]) - end - - private - - def is_negative?(number) - number.to_f.phase != 0 - end - - def absolute_value(number) - number.respond_to?("abs") ? number.abs : number.sub(/\A-/, '') - end - - def options - @options ||= begin - defaults = default_format_options.merge(i18n_opts) - # Override negative format if format options is given - defaults[:negative_format] = "-#{opts[:format]}" if opts[:format] - defaults.merge(opts) - end - end - - def i18n_opts - # Set International negative format if not exists - i18n = i18n_format_options - i18n[:negative_format] ||= "-#{i18n[:format]}" if i18n[:format] - i18n - end - - end - end -end diff --git a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb new file mode 100644 index 0000000000..113e557415 --- /dev/null +++ b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb @@ -0,0 +1,48 @@ +module ActiveSupport + module NumberHelper + class NumberToCurrencyConverter < NumberConverter # :nodoc: + + self.namespace = :currency + + def convert + number = @number.to_s.strip + format = options[:format] + + if is_negative?(number) + format = options[:negative_format] + number = absolute_value(number) + end + + rounded_number = NumberToRoundedConverter.new(number, options).execute + format.gsub('%n', rounded_number).gsub('%u', options[:unit]) + end + + private + + def is_negative?(number) + number.to_f.phase != 0 + end + + def absolute_value(number) + number.respond_to?("abs") ? number.abs : number.sub(/\A-/, '') + end + + def options + @options ||= begin + defaults = default_format_options.merge(i18n_opts) + # Override negative format if format options is given + defaults[:negative_format] = "-#{opts[:format]}" if opts[:format] + defaults.merge(opts) + end + end + + def i18n_opts + # Set International negative format if not exists + i18n = i18n_format_options + i18n[:negative_format] ||= "-#{i18n[:format]}" if i18n[:format] + i18n + end + + end + end +end diff --git a/activesupport/lib/active_support/number_helper/number_to_delimited.rb b/activesupport/lib/active_support/number_helper/number_to_delimited.rb deleted file mode 100644 index b543b0c19d..0000000000 --- a/activesupport/lib/active_support/number_helper/number_to_delimited.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'active_support/number_helper/number_converter' - -module ActiveSupport - module NumberHelper - class NumberToDelimitedConverter < NumberConverter #:nodoc: - - self.need_valid_float = true - - DELIMITED_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/ - - def convert - parts.join(options[:separator]) - end - - private - - def parts - left, right = number.to_s.split('.') - left.gsub!(DELIMITED_REGEX) { "#{$1}#{options[:delimiter]}" } - [left, right].compact - end - - end - end -end diff --git a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb new file mode 100644 index 0000000000..6604b9cce4 --- /dev/null +++ b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb @@ -0,0 +1,23 @@ +module ActiveSupport + module NumberHelper + class NumberToDelimitedConverter < NumberConverter #:nodoc: + + self.need_valid_float = true + + DELIMITED_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/ + + def convert + parts.join(options[:separator]) + end + + private + + def parts + left, right = number.to_s.split('.') + left.gsub!(DELIMITED_REGEX) { "#{$1}#{options[:delimiter]}" } + [left, right].compact + end + + end + end +end diff --git a/activesupport/lib/active_support/number_helper/number_to_human.rb b/activesupport/lib/active_support/number_helper/number_to_human.rb deleted file mode 100644 index 70d669f175..0000000000 --- a/activesupport/lib/active_support/number_helper/number_to_human.rb +++ /dev/null @@ -1,71 +0,0 @@ -require 'active_support/number_helper/number_converter' -require 'active_support/number_helper/number_to_rounded' - -module ActiveSupport - module NumberHelper - class NumberToHumanConverter < NumberConverter # :nodoc: - - DECIMAL_UNITS = { 0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion, - -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto } - INVERTED_DECIMAL_UNITS = DECIMAL_UNITS.invert - - self.namespace = :human - self.need_valid_float = true - - def convert # :nodoc: - @number = Float(@number) - - # for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files - unless options.key?(:strip_insignificant_zeros) - options[:strip_insignificant_zeros] = true - end - - units = opts[:units] - exponent = calculate_exponent(units) - @number = @number / (10 ** exponent) - - unit = determine_unit(units, exponent) - - rounded_number = NumberToRoundedConverter.new(@number, options).execute - format.gsub(/%n/, rounded_number).gsub(/%u/, unit).strip - end - - private - - def format - options[:format] || translate_in_locale('human.decimal_units.format') - end - - def determine_unit(units, exponent) - exp = DECIMAL_UNITS[exponent] - case units - when Hash - units[exp] || '' - when String, Symbol - I18n.translate("#{units}.#{exp}", :locale => options[:locale], :count => number.to_i) - else - translate_in_locale("human.decimal_units.units.#{exp}", count: number.to_i) - end - end - - def calculate_exponent(units) - exponent = number != 0 ? Math.log10(number.abs).floor : 0 - unit_exponents(units).find { |e| exponent >= e } || 0 - end - - def unit_exponents(units) - case units - when Hash - units - when String, Symbol - I18n.translate(units.to_s, :locale => options[:locale], :raise => true) - when nil - translate_in_locale("human.decimal_units.units", raise: true) - else - raise ArgumentError, ":units must be a Hash or String translation scope." - end.keys.map { |e_name| INVERTED_DECIMAL_UNITS[e_name] }.sort_by { |e| -e } - end - - end - end -end diff --git a/activesupport/lib/active_support/number_helper/number_to_human_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb new file mode 100644 index 0000000000..3cdf6ff9f8 --- /dev/null +++ b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb @@ -0,0 +1,68 @@ +module ActiveSupport + module NumberHelper + class NumberToHumanConverter < NumberConverter # :nodoc: + + DECIMAL_UNITS = { 0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion, + -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto } + INVERTED_DECIMAL_UNITS = DECIMAL_UNITS.invert + + self.namespace = :human + self.need_valid_float = true + + def convert # :nodoc: + @number = Float(@number) + + # for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files + unless options.key?(:strip_insignificant_zeros) + options[:strip_insignificant_zeros] = true + end + + units = opts[:units] + exponent = calculate_exponent(units) + @number = @number / (10 ** exponent) + + unit = determine_unit(units, exponent) + + rounded_number = NumberToRoundedConverter.new(@number, options).execute + format.gsub(/%n/, rounded_number).gsub(/%u/, unit).strip + end + + private + + def format + options[:format] || translate_in_locale('human.decimal_units.format') + end + + def determine_unit(units, exponent) + exp = DECIMAL_UNITS[exponent] + case units + when Hash + units[exp] || '' + when String, Symbol + I18n.translate("#{units}.#{exp}", :locale => options[:locale], :count => number.to_i) + else + translate_in_locale("human.decimal_units.units.#{exp}", count: number.to_i) + end + end + + def calculate_exponent(units) + exponent = number != 0 ? Math.log10(number.abs).floor : 0 + unit_exponents(units).find { |e| exponent >= e } || 0 + end + + def unit_exponents(units) + case units + when Hash + units + when String, Symbol + I18n.translate(units.to_s, :locale => options[:locale], :raise => true) + when nil + translate_in_locale("human.decimal_units.units", raise: true) + else + raise ArgumentError, ":units must be a Hash or String translation scope." + end.keys.map { |e_name| INVERTED_DECIMAL_UNITS[e_name] }.sort_by { |e| -e } + end + + end + end +end diff --git a/activesupport/lib/active_support/number_helper/number_to_human_size.rb b/activesupport/lib/active_support/number_helper/number_to_human_size.rb deleted file mode 100644 index c0930564bc..0000000000 --- a/activesupport/lib/active_support/number_helper/number_to_human_size.rb +++ /dev/null @@ -1,63 +0,0 @@ -require 'active_support/number_helper/number_converter' -require 'active_support/number_helper/number_to_rounded' - -module ActiveSupport - module NumberHelper - class NumberToHumanSizeConverter < NumberConverter - - STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb] - - self.namespace = :human - self.need_valid_float = true - - def convert - @number = Float(@number) - - # for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files - unless options.key?(:strip_insignificant_zeros) - options[:strip_insignificant_zeros] = true - end - - if smaller_than_base? - number_to_format = @number.to_i.to_s - else - human_size = @number / (base ** exponent) - number_to_format = NumberToRoundedConverter.new(human_size, options).execute - end - conversion_format.gsub(/%n/, number_to_format).gsub(/%u/, unit) - end - - private - - def conversion_format - translate_number_value_with_default('human.storage_units.format', :locale => options[:locale], :raise => true) - end - - def unit - translate_number_value_with_default(storage_unit_key, :locale => options[:locale], :count => @number.to_i, :raise => true) - end - - def storage_unit_key - key_end = smaller_than_base? ? 'byte' : STORAGE_UNITS[exponent] - "human.storage_units.units.#{key_end}" - end - - def exponent - max = STORAGE_UNITS.size - 1 - exp = (Math.log(@number) / Math.log(base)).to_i - exp = max if exp > max # avoid overflow for the highest unit - exp - end - - def smaller_than_base? - @number.to_i < base - end - - def base - opts[:prefix] == :si ? 1000 : 1024 - end - - end - end -end - diff --git a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb new file mode 100644 index 0000000000..6a0f6e70b7 --- /dev/null +++ b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb @@ -0,0 +1,60 @@ +module ActiveSupport + module NumberHelper + class NumberToHumanSizeConverter < NumberConverter + + STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb] + + self.namespace = :human + self.need_valid_float = true + + def convert + @number = Float(@number) + + # for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files + unless options.key?(:strip_insignificant_zeros) + options[:strip_insignificant_zeros] = true + end + + if smaller_than_base? + number_to_format = @number.to_i.to_s + else + human_size = @number / (base ** exponent) + number_to_format = NumberToRoundedConverter.new(human_size, options).execute + end + conversion_format.gsub(/%n/, number_to_format).gsub(/%u/, unit) + end + + private + + def conversion_format + translate_number_value_with_default('human.storage_units.format', :locale => options[:locale], :raise => true) + end + + def unit + translate_number_value_with_default(storage_unit_key, :locale => options[:locale], :count => @number.to_i, :raise => true) + end + + def storage_unit_key + key_end = smaller_than_base? ? 'byte' : STORAGE_UNITS[exponent] + "human.storage_units.units.#{key_end}" + end + + def exponent + max = STORAGE_UNITS.size - 1 + exp = (Math.log(@number) / Math.log(base)).to_i + exp = max if exp > max # avoid overflow for the highest unit + exp + end + + def smaller_than_base? + @number.to_i < base + end + + def base + opts[:prefix] == :si ? 1000 : 1024 + end + + end + end +end + diff --git a/activesupport/lib/active_support/number_helper/number_to_percentage.rb b/activesupport/lib/active_support/number_helper/number_to_percentage.rb deleted file mode 100644 index 25e136be60..0000000000 --- a/activesupport/lib/active_support/number_helper/number_to_percentage.rb +++ /dev/null @@ -1,17 +0,0 @@ -require 'active_support/number_helper/number_converter' -require 'active_support/number_helper/number_to_rounded' - -module ActiveSupport - module NumberHelper - class NumberToPercentageConverter < NumberConverter # :nodoc: - - self.namespace = :percentage - - def convert - rounded_number = NumberToRoundedConverter.new(number, options).execute - options[:format].gsub('%n', rounded_number) - end - - end - end -end diff --git a/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb b/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb new file mode 100644 index 0000000000..b7926cf151 --- /dev/null +++ b/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb @@ -0,0 +1,14 @@ +module ActiveSupport + module NumberHelper + class NumberToPercentageConverter < NumberConverter # :nodoc: + + self.namespace = :percentage + + def convert + rounded_number = NumberToRoundedConverter.new(number, options).execute + options[:format].gsub('%n', rounded_number) + end + + end + end +end diff --git a/activesupport/lib/active_support/number_helper/number_to_phone.rb b/activesupport/lib/active_support/number_helper/number_to_phone.rb deleted file mode 100644 index 171c2dff18..0000000000 --- a/activesupport/lib/active_support/number_helper/number_to_phone.rb +++ /dev/null @@ -1,54 +0,0 @@ -require 'active_support/number_helper/number_converter' -require 'active_support/number_helper/number_to_rounded' - -module ActiveSupport - module NumberHelper - class NumberToPhoneConverter < NumberConverter - def convert - str = '' - str << country_code(opts[:country_code]) - str << convert_to_phone_number(@number.to_s.strip) - str << phone_ext(opts[:extension]) - end - - private - - def convert_to_phone_number(number) - if opts[:area_code] - convert_with_area_code(number) - else - convert_without_area_code(number) - end - end - - def convert_with_area_code(number) - number.gsub(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3") - end - - def convert_without_area_code(number) - number.tap { |n| - n.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3") - n.slice!(0, 1) if begins_with_delimiter?(n) - } - end - - def begins_with_delimiter?(number) - number.start_with?(delimiter) && !delimiter.blank? - end - - def delimiter - opts[:delimiter] || "-" - end - - def country_code(code) - code.blank? ? "" : "+#{code}#{delimiter}" - end - - def phone_ext(ext) - ext.blank? ? "" : " x #{ext}" - end - - end - end -end - diff --git a/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb new file mode 100644 index 0000000000..c4aa5c311a --- /dev/null +++ b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb @@ -0,0 +1,51 @@ +module ActiveSupport + module NumberHelper + class NumberToPhoneConverter < NumberConverter + def convert + str = '' + str << country_code(opts[:country_code]) + str << convert_to_phone_number(@number.to_s.strip) + str << phone_ext(opts[:extension]) + end + + private + + def convert_to_phone_number(number) + if opts[:area_code] + convert_with_area_code(number) + else + convert_without_area_code(number) + end + end + + def convert_with_area_code(number) + number.gsub(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3") + end + + def convert_without_area_code(number) + number.tap { |n| + n.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3") + n.slice!(0, 1) if begins_with_delimiter?(n) + } + end + + def begins_with_delimiter?(number) + number.start_with?(delimiter) && !delimiter.blank? + end + + def delimiter + opts[:delimiter] || "-" + end + + def country_code(code) + code.blank? ? "" : "+#{code}#{delimiter}" + end + + def phone_ext(ext) + ext.blank? ? "" : " x #{ext}" + end + + end + end +end + diff --git a/activesupport/lib/active_support/number_helper/number_to_rounded.rb b/activesupport/lib/active_support/number_helper/number_to_rounded.rb deleted file mode 100644 index 817ac1ebb2..0000000000 --- a/activesupport/lib/active_support/number_helper/number_to_rounded.rb +++ /dev/null @@ -1,62 +0,0 @@ -require 'active_support/number_helper/number_converter' - -module ActiveSupport - module NumberHelper - class NumberToRoundedConverter < NumberConverter # :nodoc: - - self.namespace = :precision - self.need_valid_float = true - - def convert - @number = Float(@number) - - precision = options.delete :precision - significant = options.delete :significant - - if significant && precision > 0 - digits, rounded_number = digits_and_rounded_number(precision) - precision -= digits - precision = 0 if precision < 0 # don't let it be negative - else - rounded_number = BigDecimal.new(@number.to_s).round(precision).to_f - rounded_number = rounded_number.abs if rounded_number.zero? # prevent showing negative zeros - end - delimited_number = NumberToDelimitedConverter.new("%01.#{precision}f" % rounded_number, options).execute - format_number(delimited_number) - end - - private - - def digits_and_rounded_number(precision) - return [1,0] if @number.zero? - digits = digit_count(@number) - multiplier = 10 ** (digits - precision) - rounded_number = calculate_rounded_number(multiplier) - digits = digit_count(rounded_number) # After rounding, the number of digits may have changed - [digits, rounded_number] - end - - def calculate_rounded_number(multiplier) - (BigDecimal.new(@number.to_s) / BigDecimal.new(multiplier.to_f.to_s)).round.to_f * multiplier - end - - def digit_count(number) - (Math.log10(number.abs) + 1).floor - end - - def strip_insignificant_zeros - options[:strip_insignificant_zeros] - end - - def format_number(number) - if strip_insignificant_zeros - escaped_separator = Regexp.escape(options[:separator]) - number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '') - else - number - end - end - - end - end -end diff --git a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb new file mode 100644 index 0000000000..355c810dc9 --- /dev/null +++ b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb @@ -0,0 +1,60 @@ +module ActiveSupport + module NumberHelper + class NumberToRoundedConverter < NumberConverter # :nodoc: + + self.namespace = :precision + self.need_valid_float = true + + def convert + @number = Float(@number) + + precision = options.delete :precision + significant = options.delete :significant + + if significant && precision > 0 + digits, rounded_number = digits_and_rounded_number(precision) + precision -= digits + precision = 0 if precision < 0 # don't let it be negative + else + rounded_number = BigDecimal.new(@number.to_s).round(precision).to_f + rounded_number = rounded_number.abs if rounded_number.zero? # prevent showing negative zeros + end + delimited_number = NumberToDelimitedConverter.new("%01.#{precision}f" % rounded_number, options).execute + format_number(delimited_number) + end + + private + + def digits_and_rounded_number(precision) + return [1,0] if @number.zero? + digits = digit_count(@number) + multiplier = 10 ** (digits - precision) + rounded_number = calculate_rounded_number(multiplier) + digits = digit_count(rounded_number) # After rounding, the number of digits may have changed + [digits, rounded_number] + end + + def calculate_rounded_number(multiplier) + (BigDecimal.new(@number.to_s) / BigDecimal.new(multiplier.to_f.to_s)).round.to_f * multiplier + end + + def digit_count(number) + (Math.log10(number.abs) + 1).floor + end + + def strip_insignificant_zeros + options[:strip_insignificant_zeros] + end + + def format_number(number) + if strip_insignificant_zeros + escaped_separator = Regexp.escape(options[:separator]) + number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '') + else + number + end + end + + end + end +end -- cgit v1.2.3