# Printing human-readable number of bytes

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

• 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. – Benjamin Manns Sep 10 '12 at 13:47

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

• 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 – ujifgc Mar 3 '12 at 7:40

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

• 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. – ujifgc Feb 20 '12 at 10:25