Subscribed unsubscribe Subscribe Subscribe

枕を欹てて聴く

香炉峰の雪は簾を撥げて看る

Human Readable File Size

Ruby

ls -hすると

1.4K
1003
10M

というファイルサイズの表示になります.

  • hはhuman readableのhなんですね!

で, この数字を表示したい! となると面倒です...
以下にさまざまな例を出すので場合によって使い分けるといいかも.

Integer Ver

すぐ思いつくのはこれでしょうか.

@bytes = %w(B K M G T P E Z Y)
def humanize_number1 size
  cnt = 0
  loop do
    break if size < 1024 && cnt < 8
    size /= 1024.0
    cnt += 1
  end
  sprintf("%i%s", size, @bytes[cnt])
end

きちんと表示されます. ありがたや.
しかし, 少し工夫すればこんな風にも,

@bytes = %w(B K M G T P E Z Y)
def humanize_number2 size
  cnt = (Math.log(size) / Math.log(1024)).to_i
  cnt = 8 if cnt > 8
  size /= 1024.0 ** cnt
  sprintf("%i%s", size, @bytes[cnt])
end

これはこれでいいものです. 用途次第.
しかし, 今回の目標はlsやdfの表示.
よく見るとlsやdfの表示は 1 < size < 10の時には小数点以下一桁も表示してますねー.
悔しいです! ということで...

小数点以下表示

@bytes = %w(B K M G T P E Z Y)
def humanize_number3 size
  cnt = 0
  loop do
    break if size < 1024 && cnt < 8
    size /= 1024.0
    cnt += 1
  end
  if 0 < size && size < 10
    sprintf("%.1f%s", size, @bytes[cnt])
  else
    sprintf("%i%s", size, @bytes[cnt])
  end
end

これで小数点以下が表示されます. 0 < sizeとしているのは, size => 0のときに0.0なんて表示にしないようにするためです.
しかし, lsの結果と数字が合わないのではないでしょうか.
これには問題があり, %.1fは見えない桁が5より上のとき切り上げてしまいます.
四捨五入でもなく5では切り上げないのに51とかだと切り上げる. 正直使いにくすぎます.
さらに, 実はよく見ると, ls結果は常に切り上げをしています.
=> 1024*1025は1.1M

Ceil

@bytes = %w(B K M G T P E Z Y)
def humanize_number4 size
  cnt = 0
  loop do
    break if size < 1024 && cnt < 8
    size /= 1024.0
    cnt += 1
  end
  if 0 < size && size < 10
    sprintf("%.1f%s", ((size*10.0).ceil)/ 10.0, @bytes[cnt])
  else
    sprintf("%i%s", size.ceil, @bytes[cnt])
  end
end

ceilによる切り上げ. 0 < size < 10のときは?.?XのXを切り上げ, そのほかの場合は???.XのXを切り上げます.
これなら%.1fのときも見えない桁は常に0となるので安心できます.
しかし, まだです. たとえば0 < size < 10の時点で, sizeが 9.1などになったときどうでしょうか. 10.0Mなどと間抜けな表示がされてしまいます. 10Mという風に表示すべきです.

10.0の可能性削除

@bytes = %w(B K M G T P E Z Y)
def humanize_number5 size
  cnt = 0
  loop do
    break if size < 1024 && cnt < 8
    size /= 1024.0
    cnt += 1
  end
  if 0 < size && size <= 9
    sprintf("%.1f%s", ((size*10.0).ceil)/ 10.0, @bytes[cnt])
  else
    sprintf("%i%s", size.ceil, @bytes[cnt])
  end
end

size < 10を size <= 9にすることで, 10.0になってしまうことを防ぎます. これでやっと完成? いえまだです.
今度はsizeが 1023.1のときを考えてください. このままでは1024Kなどと間抜けな表示をしてしまいます. ちなみにlsではきちんと1.0Mと表示します.

1024の可能性削除

@bytes = %w(B K M G T P E Z Y)
def humanize_number6 size
  cnt = 0
  loop do
    break if size <= 1023 && cnt < 8
    size /= 1024.0
    cnt += 1
  end
  puts size
  if 0 < size && size <= 9
    sprintf("%.1f%s", ((size*10.0).ceil)/10.0, @bytes[cnt])
  else
    sprintf("%i%s", size.ceil, @bytes[cnt])
  end
end

割るときの条件を1023以下とすることで1023より大きい時は1024.0でまだ割ってしまいます.
これでようやく完成です.

補足

なんか見落としてそうなので気づいた方は教えていただけるとありがたいです.