/* Simple byte size formatting. This package implements types that can be used in stdlib formatting functions like `fmt.Printf` to control the output of the expected printed string. Floating point flags %f and %g print the value in using the correct unit suffix. Decimal units are default, # switches to binary units. If a value is best represented as full bytes, integer bytes are printed instead. Examples: fmt.Printf("%.2f", 123 * B) => "123B" fmt.Printf("%.2f", 1234 * B) => "1.23kB" fmt.Printf("%g", 1200 * B) => "1.2kB" fmt.Printf("%#g", 1024 * B) => "1KiB" Integer flag %d always prints the value in bytes. # flag adds an unit prefix. Examples: fmt.Printf("%d", 1234 * B) => "1234" fmt.Printf("%#d", 1234 * B) => "1234B" %v is equal to %g */ package units import ( "fmt" "io" "math" "math/big" ) type Bytes int64 const ( B Bytes = 1 << (10 * iota) KiB MiB GiB TiB PiB EiB KB = 1e3 * B MB = 1e3 * KB GB = 1e3 * MB TB = 1e3 * GB PB = 1e3 * TB EB = 1e3 * PB ) var units = map[bool][]string{ false: []string{ "B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", }, true: []string{ "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", }, } func (b Bytes) Format(f fmt.State, c rune) { switch c { case 'f', 'g': fv, unit, ok := b.floatValue(f.Flag('#')) if !ok { b.formatInt(f, 'd', true) return } big.NewFloat(fv).Format(f, c) io.WriteString(f, unit) case 'd': b.formatInt(f, c, f.Flag('#')) default: if f.Flag('#') { fmt.Fprintf(f, "bytes(%d)", int64(b)) } else { fmt.Fprintf(f, "%g", b) } } } func (b Bytes) formatInt(f fmt.State, c rune, withUnit bool) { big.NewInt(int64(b)).Format(f, c) if withUnit { io.WriteString(f, "B") } } func (b Bytes) floatValue(binary bool) (float64, string, bool) { i := 0 var baseUnit Bytes = 1 if b < 0 { baseUnit *= -1 } for { next := baseUnit if binary { next *= 1 << 10 } else { next *= 1e3 } if (baseUnit > 0 && b >= next) || (baseUnit < 0 && b <= next) { i++ baseUnit = next continue } if i == 0 { return 0, "", false } return float64(b) / math.Abs(float64(baseUnit)), units[binary][i], true } }