Format and Parse Human-Readable Byte Sizes
Format byte counts with binary units and parse human-readable sizes back into integers with rounding and unit validation.
Code
package main
import (
"fmt"
"math"
"strconv"
"strings"
)
var units = []string{"B", "KB", "MB", "GB", "TB", "PB"}
func formatBytes(value uint64) string {
if value < 1024 {
return fmt.Sprintf("%d B", value)
}
amount := float64(value)
unit := 0
for amount >= 1024 && unit < len(units)-1 {
amount /= 1024
unit++
}
precision := 0
if amount < 10 {
precision = 2
} else if amount < 100 {
precision = 1
}
return strconv.FormatFloat(amount, 'f', precision, 64) + " " + units[unit]
}
func parseBytes(text string) (uint64, error) {
fields := strings.Fields(strings.ToUpper(text))
if len(fields) != 2 {
return 0, fmt.Errorf("expected value and unit, got %q", text)
}
number, err := strconv.ParseFloat(fields[0], 64)
if err != nil || number < 0 {
return 0, fmt.Errorf("invalid size %q", fields[0])
}
index := -1
for candidate, unit := range units {
if unit == fields[1] {
index = candidate
break
}
}
if index < 0 {
return 0, fmt.Errorf("unknown unit %q", fields[1])
}
scale := math.Pow(1024, float64(index))
return uint64(math.Round(number * scale)), nil
}
func main() {
cases := []struct {
value uint64
want string
}{
{0, "0 B"},
{999, "999 B"},
{1024, "1.00 KB"},
{1536, "1.50 KB"},
{10 * 1024 * 1024, "10.0 MB"},
}
for _, tc := range cases {
got := formatBytes(tc.value)
fmt.Printf("%d -> %s\n", tc.value, got)
if got != tc.want {
panic("unexpected formatting: " + got)
}
}
parsed, err := parseBytes("1.5 MB")
if err != nil || parsed != 1572864 {
panic("unexpected parse result")
}
if _, err := parseBytes("12 XB"); err == nil {
panic("unknown unit should fail")
}
}
Report a problem with this exampleOpens a pre-filled issue on GitHub.