3
\$\begingroup\$
def as_size( s )
 prefix = %W(TiB GiB MiB KiB B)
 s = s.to_f
 i = prefix.length - 1
 while s > 512 && i > 0
 s /= 1024
 i -= 1
 end
 ("%#{s > 9 ? 'd' : '.1f'} #{prefix[i]}" % s).gsub /\.0/, ''
end

I'd like a clean and fast code to convert raw file size to human-readable format. Any hints?

Update (faster format):

def as_size( s )
 prefix = %W(TiB GiB MiB KiB B)
 s = s.to_f
 i = prefix.length - 1
 while s > 512 && i > 0
 s /= 1024
 i -= 1
 end
 ((s > 9 || s.modulo(1) < 0.1 ? '%d' : '%.1f') % s) + ' ' + prefix[i]
end

Update (freeze the constant):

PREFIX = %W(TiB GiB MiB KiB B).freeze
def as_size( s )
 s = s.to_f
 i = PREFIX.length - 1
 while s > 512 && i > 0
 i -= 1
 s /= 1024
 end
 ((s > 9 || s.modulo(1) < 0.1 ? '%d' : '%.1f') % s) + ' ' + PREFIX[i]
end
asked Feb 18, 2012 at 13:16
\$\endgroup\$
1
  • 2
    \$\begingroup\$ I wouldn't worry so much about the performance of this code. I ran the benchmark that you provided, and found that your code is 1 microsecond faster than Jordan's, which is 1 microsecond faster than Alex's. If you need to worry about microseconds in string formatting, you ought not be using Ruby. Use whatever version of the code reads best and makes the most sense to you and the guy who will have to maintain your code. \$\endgroup\$ Commented Sep 10, 2012 at 13:47

3 Answers 3

3
\$\begingroup\$

It's worth looking at how Rails does this in its number_to_human_size helper. Rather than iteration it just calculates the exponent (log x / log 1024). Paring it down to the bare essentials it looks something like this:

UNITS = %W(B KiB MiB GiB TiB).freeze
def as_size number
 if number.to_i < 1024
 exponent = 0
 else
 max_exp = UNITS.size - 1
 exponent = ( Math.log( number ) / Math.log( 1024 ) ).to_i # convert to base
 exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
 number /= 1024 ** exponent
 end
 "#{number} #{UNITS[ exponent ]}"
end
answered Mar 3, 2012 at 3:28
\$\endgroup\$
1
  • \$\begingroup\$ Good one on freezing the constant, thanks, it speeds up a lot! But still, your suggestion is ~20% slower than simple loop. You can run the benchmark, I updated it with the new code: gist.github.com/1868681 \$\endgroup\$ Commented Mar 3, 2012 at 7:40
5
\$\begingroup\$

Possible solution: reduce your input to required precision. Returned result construction also reads cleaner with this approach:

def as_size(s)
 units = %W(B KiB MiB GiB TiB)
 size, unit = units.reduce(s.to_f) do |(fsize, _), utype|
 fsize > 512 ? [fsize / 1024, utype] : (break [fsize, utype])
 end
 "#{size > 9 || size.modulo(1) < 0.1 ? '%d' : '%.1f'} %s" % [size, unit]
end
answered Feb 18, 2012 at 14:23
\$\endgroup\$
1
  • \$\begingroup\$ Thanks for the modulo and format tips, but the reduce approach here is a bit cryptic and 1.5 times slower. Here's a gist gist.github.com/1868681 of the benchmark. \$\endgroup\$ Commented Feb 20, 2012 at 10:25
1
\$\begingroup\$

Another solution is to add the method to the Numeric class,

class Numeric
 PREFIX = %W(TiB GiB MiB KiB B).freeze
 def to_bibyte
 s = self.to_f
 i = PREFIX.length - 1
 while s > 512 && i > 0
 i -= 1
 s /= 1024
 end
 ((s > 9 || s.modulo(1) < 0.1 ? '%d' : '%.1f') % s) + ' ' + PREFIX[i]
 end
end

and then call it like,

i=123049158 puts i.to_bibyte

answered Nov 24, 2020 at 11:23
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.