I once had a feature request for “human-readable bytes like ls -lh
”.
Surely, there must be a library for that, I thought.
But ls -lh
has special, irregular semantics I couldn’t find all implemented
together in the same library!
- Use no decimal or unit suffix for values under 1024 bytes: 0 or 897
- Use one decimal if the integer part is a single digit: 1.0 K or 9.9 M
- Use no decimal if the integer part is more than one digit: 10 K or 582 M
- Aggressively round up: 999 M + 1 byte rounds up to 1 G
If you ever have a similar request, here’s some Go code you can use. Unlike
ls -lh
, this does add a space between the number and the suffix. It’s also
generic to any base or suffixes you want. See my demo
repo for more examples and test cases.
type format struct {
base float64
logBase float64
suffixes []string
}
var formatLS = format{
base: 1024,
logBase: math.Log(1024),
suffixes: []string{"", " K", " M", " G", " T", " P", " E", " Z", " Y"},
}
func SizeLS(size int) string {
return humanSize(float64(size), formatLS)
}
func humanSize(size float64, f format) string {
if size == 0 {
return "0"
}
mag := math.Floor(math.Log(size) / f.logBase)
size /= math.Pow(f.base, mag)
switch {
case mag == 0:
// do nothing
case size < 10:
size = math.Ceil(size*10) / 10
default:
size = math.Ceil(size)
}
if size >= f.base {
size /= f.base
mag++
}
format := "%.1f%s"
if mag == 0 || size >= 10 {
format = "%.0f%s"
}
return fmt.Sprintf(format, size, f.suffixes[int(mag)])
}