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
-
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– Benjamin Manns2012年09月10日 13:47:36 +00:00Commented Sep 10, 2012 at 13:47
3 Answers 3
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
-
\$\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– ujifgc2012年03月03日 07:40:10 +00:00Commented Mar 3, 2012 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
-
\$\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– ujifgc2012年02月20日 10:25:49 +00:00Commented Feb 20, 2012 at 10:25
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