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.

GoCli Tooling

Code

snippet
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")
	}
}