#-- # Author:: Tyler Rick # Copyright:: Ruby on Rails developers # License:: Ruby on Rails license # Submit to Facets?:: yes # Developer notes:: # History:: #++ require 'facets/hash/symbolize_keys' class Array def extract_options! last.is_a?(::Hash) ? pop : {} end end #------------------------------------------------------------------------------- module Kernel # TODO: use quality_extensions/helpers/numbers instead # Adapted from /var/lib/gems/1.9.1/gems/actionpack-2.3.4/lib/action_view/helpers/number_helper.rb # Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You can # customize the format in the +options+ hash. # # ==== Options # * :delimiter - Sets the thousands delimiter (defaults to ","). # * :separator - Sets the separator between the units (defaults to "."). # # ==== Examples # number_with_delimiter(12345678) # => 12,345,678 # number_with_delimiter(12345678.05) # => 12,345,678.05 # number_with_delimiter(12345678, :delimiter => ".") # => 12.345.678 # number_with_delimiter(12345678, :separator => ",") # => 12,345,678 # number_with_delimiter(98765432.98, :delimiter => " ", :separator => ",") # # => 98 765 432,98 # # You can still use number_with_delimiter with the old API that accepts the # +delimiter+ as its optional second and the +separator+ as its # optional third parameter: # number_with_delimiter(12345678, " ") # => 12 345.678 # number_with_delimiter(12345678.05, ".", ",") # => 12.345.678,05 def number_with_delimiter(number, *args) options = args.extract_options! options.symbolize_keys! unless args.empty? ActiveSupport::Deprecation.warn('number_with_delimiter takes an option hash ' + 'instead of separate delimiter and precision arguments.', caller) delimiter = args[0] || '.' separator = args[1] || ',' end delimiter ||= (options[:delimiter] || '.') separator ||= (options[:separator] || ',') begin parts = number.to_s.split('.') parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}") parts.join(separator) #rescue # number end end # Formats a +number+ with the specified level of :precision (e.g., 112.32 has a precision of 2). # You can customize the format in the +options+ hash. # # ==== Options # * :precision - Sets the level of precision (defaults to 3). # * :separator - Sets the separator between the units (defaults to "."). # * :delimiter - Sets the thousands delimiter (defaults to ""). # # ==== Examples # number_with_precision(111.2345) # => 111.235 # number_with_precision(111.2345, :precision => 2) # => 111.23 # number_with_precision(13, :precision => 5) # => 13.00000 # number_with_precision(389.32314, :precision => 0) # => 389 # number_with_precision(1111.2345, :precision => 2, :separator => ',', :delimiter => '.') # # => 1.111,23 # # You can still use number_with_precision with the old API that accepts the # +precision+ as its optional second parameter: # number_with_precision(number_with_precision(111.2345, 2) # => 111.23 def number_with_precision(number, *args) options = args.extract_options! options.symbolize_keys! precision ||= (options[:precision] || 3) separator ||= (options[:separator] || '.') delimiter ||= (options[:delimiter] || '') begin rounded_number = (Float(number) * (10 ** precision)).round.to_f / 10 ** precision number_with_delimiter("%01.#{precision}f" % rounded_number, :separator => separator, :delimiter => delimiter) #rescue # number end end # Formats the bytes in +size+ into a more understandable representation # (e.g., giving it 1500 yields 1.5 KB). This method is useful for # reporting file sizes to users. This method returns nil if # +size+ cannot be converted into a number. You can customize the # format in the +options+ hash. # # ==== Options # * :base - Pass in 2 (or 1024) to use binary units (KiB, MiB), # pass in 10 (or 1000) to use SI (decimal) units (KB, MB) # (defaults to base 10). # * :precision - Sets the level of precision (defaults to 1). # * :separator - Sets the separator between the units (defaults to "."). # * :delimiter - Sets the thousands delimiter (defaults to ""). # # ==== Examples # number_to_human_size(123) # => 123 Bytes # number_to_human_size(1234) # => 1.2 KB # number_to_human_size(12345) # => 12.3 KB # number_to_human_size(1234567) # => 1.2 MB # number_to_human_size(1234567890) # => 1.2 GB # number_to_human_size(1234567890123) # => 1.2 TB # number_to_human_size(1234567, :precision => 2) # => 1.23 MB # number_to_human_size(1234567, :precision => 2, :base => 2) # => 1.18 MiB # number_to_human_size(483989, :precision => 0) # => 484 KB # number_to_human_size(483989, :precision => 0, :base => 2) # => 473 KiB # number_to_human_size(1234567, :precision => 2, :separator => ',') # => 1,23 MB # # ==== Differences from ActiveSupport version # The ActiveSupport version defaults to binary (base 2) units, while this one # defaults to SI (base 10) units. # # The ActiveSupport incorrectly uses KB to refer to binary units, when the correct # abbreviation would be KiB (see http://en.wikipedia.org/wiki/Binary_prefix). # # This version has a :base option to let you change the base; the ActiveSupport # version does not. # def number_to_human_size(number, *args) return nil if number.nil? options = args.extract_options! options.symbolize_keys! precision ||= (options[:precision] || 1) separator ||= (options[:separator] || '.') delimiter ||= (options[:delimiter] || ',') base ||= (options[:base] || 10) # http://en.wikipedia.org/wiki/Binary_prefix if base == 10 || base == 1000 storage_units = %w( Bytes KB MB GB TB ).freeze base = 1000 elsif base == 2 || base == 1024 storage_units = %w( Bytes KiB MiB GiB TiB ).freeze base = 1024 else raise ArgumentError, "base must be 1000 or 1024" end storage_units_format = '%n %u' if number.to_i < base unit = number.to_i == 1 ? 'byte' : 'bytes' storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit) else max_exp = storage_units.size - 1 number = Float(number) exponent = (Math.log(number) / Math.log(base)).to_i # Convert to base 1024 exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit number /= base ** exponent unit = storage_units[exponent] begin escaped_separator = Regexp.escape(separator) formatted_number = number_with_precision(number, :precision => precision, :separator => separator, :delimiter => delimiter ).sub(/(\d)(#{escaped_separator}[1-9]*)?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '') storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit) #rescue # number end end end end # _____ _ # |_ _|__ ___| |_ # | |/ _ \/ __| __| # | | __/\__ \ |_ # |_|\___||___/\__| # =begin test require 'spec/autorun' describe 'number_to_human_size' do it 'uses decimal unit (KB) when using base 1000' do number_to_human_size(524288, :base => 1000).should match(/ KB$/) end it 'uses the correct conversion when using base 1000' do number_to_human_size(524288, :base => 1000).should match(/^524\.3 /) end it 'uses binary unit (KiB) when using base 1024' do number_to_human_size(524288, :base => 1024).should match(/ KiB$/) end it 'uses the correct conversion when using base 1024' do number_to_human_size(524288, :base => 1024).should match(/^512 /) end end describe 'number_to_human_size examples' do it '1234' do number_to_human_size(123).should == '123 bytes' end it '12345' do number_to_human_size(12345).should == '12.3 KB' end it '1234567' do number_to_human_size(1234567).should == '1.2 MB' number_to_human_size(1234567, :base => 2).should == '1.2 MiB' end it '1234567890' do number_to_human_size(1234567890).should == '1.2 GB' end it '1234567890123' do number_to_human_size(1234567890123).should == '1.2 TB' end it '1234567' do number_to_human_size(1234567, :precision => 2).should == '1.23 MB' number_to_human_size(1234567, :precision => 2, :base => 2).should == '1.18 MiB' end it '483989' do number_to_human_size(483989, :precision => 0).should == '484 KB' number_to_human_size(483989, :precision => 0, :base => 2).should == '473 KiB' end it '1234567' do number_to_human_size(1234567, :precision => 2, :separator => ',').should == '1,23 MB' end end =end