2
\$\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
\$\endgroup\$
  • 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\$ – Benjamin Manns Sep 10 '12 at 13:47
2
\$\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
\$\endgroup\$
  • \$\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\$ – ujifgc Mar 3 '12 at 7:40
4
\$\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
\$\endgroup\$
  • \$\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\$ – ujifgc Feb 20 '12 at 10:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.