功能说明
这段代码解决两个方向的转换:把字节数格式化成 1.50 KB、10.0 MB 这样的可读文本,以及把用户输入的 1.5 MB 解析回精确的字节数。它适合命令行工具的 --size 参数、磁盘占用报表和日志展示。
格式化会根据数量级自动选择精度:小于 10 保留两位小数,小于 100 保留一位,更大的值不保留小数,避免输出过长又保留有效信息。
关键代码
formatBytes 对小于 1024 的值直接输出字节;否则不断除以 1024 并提升单位,直到数值小于 1024 或到达最大单位。strconv.FormatFloat 负责定点格式化,保证不会出现科学计数法。
parseBytes 先用 strings.Fields 拆分数字和单位,统一转大写,再用 strconv.ParseFloat 解析数字。单位通过遍历 units 找到下标,最后乘以 1024^index 并用 math.Round 四舍五入成整数。
使用注意
这里的 KB、MB 实际采用 1024 进制,语义上更接近 KiB、MiB;如果面向普通用户或需要十进制单位,应改成 1000 并调整文案。解析器不接受负数,也不接受 1,5 MB 这类本地化小数格式。
极大值乘以缩放因子后可能超出 uint64,生产代码应检查范围。格式化结果用于比较或排序时不可靠,应保留原始整数。解析只接受“数字 + 单位”两个字段,1.5MB 需要先用正则拆分。
验证方式
示例断言 0 输出 0 B、999 输出 999 B、1024 输出 1.00 KB、1536 输出 1.50 KB、10 MiB 输出 10.0 MB。parseBytes("1.5 MB") 得到 1572864 字节;单位写成 XB 会返回错误。可以再加入 1 PB 检查最大单位路径。
代码示例
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")
}
}