首页
/ Moby 仓库内嵌 go-humanize 深入指南:在 Go 中人性化格式化字节、时间、序数与 SI 数值

Moby 仓库内嵌 go-humanize 深入指南:在 Go 中人性化格式化字节、时间、序数与 SI 数值

2026-09-07 20:21:48作者:温玫谨Lighthearted

本指南以 Moby 仓库 vendor 树中随包携带的 vendor/github.com/dustin/go-humanize/README.markdown 为蓝本,结合其同一目录下的全部源码实现,系统讲解 go-humanize 在 Go 语言中把枯燥的数字转成人话字符串的完整能力:字节容量(SI/IEC)、相对时间、序数词、千分位逗号、浮点清理、SI 科学前缀以及英文复数/词语串联。读完你能准确理解每个 API 的输出边界与底层算法,并能在自己的 Go 项目中直接、正确地使用这套工具。

库在仓库中的位置与导入方式

在 Moby 仓库中,go-humanize 以 vendored 第三方依赖的形式存放在 vendor/github.com/dustin/go-humanize/ 目录下,包内结构一目了然:

文件 职责
bytes.go SI/IEC 字节格式化与解析(Bytes/IBytes/ParseBytes
bigbytes.go 基于 big.Int 的超大字节格式化与解析
times.go 相对时间格式化(Time/RelTime/CustomRelTime
ordinals.go 序数词后缀(Ordinal
comma.go 千分位逗号(Comma/Commaf/BigComma
ftoa.go 浮点字符串清理(Ftoa
si.go SI 前缀格式化与解析(SI/ComputeSI/ParseSI
big.go big.Int 量级计算辅助函数
number.go 模板友好的自定义数字格式(FormatFloat

包自身的用途在 humanize.go 的包注释里写得很直白:把无聊难看的数字转成对用户友好的字符串(以及再转回来)。导入方式即标准的:

import "github.com/dustin/go-humanize" // 使用时写作 humanize

注:本仓库作为只读的 vendor 快照,仅内嵌了 go-humanize 的顶层包源码;README 中提到的 humanize/english 子包属于上游完整发行版的一部分(见下文“英文专用函数”一节)。在 Moby 中依赖的即是本目录这份实现。

字节容量:SI 与 IEC 两种进制一网打尽

大小格式化是 go-humanize 最常用的能力,它让你把 82854982 这样的裸字节数直接变成 83 MB79 MiB。核心入口是两个函数(bytes.go):

fmt.Printf("That file is %s.", humanize.Bytes(82854982))   // That file is 83 MB.
fmt.Printf("That file is %s.", humanize.IBytes(82854982))  // That file is 79 MiB.
  • Bytes(SI 十进制,基 1000):依次使用 B, kB, MB, GB, TB, PB, EB 后缀;
  • IBytes(IEC 二进制,基 1024):依次使用 B, KiB, MiB, GiB, TiB, PiB, EiB 后缀。

底层由同一个 humanateBytes(s, base, sizes) 函数驱动,算法要点值得了解:

  1. 字节数小于 10 时直接返回整数形式 "%d B"
  2. 通过换底公式 log(n)/log(base) 计算数量级指数 e,据此取出对应后缀;
  3. 数值保留 1 位小数并做四舍五入val = floor(s/base^e*10 + 0.5) / 10
  4. 若换算后 val >= 10,格式化模板退化为 "%.0f %s",去掉小数位——这就是 82854982 经 SI 换算得到 82.9 → 显示为整数 83 MB 的原因。

对应的单位常量与后缀解析表也定义在同一文件中:bytes.go。IEC 常量用 1 << (iota * 10) 递推得到 Byte=1, KiByte=1024, …;SI 常量按 *1000 递推;bytesSizeTableb/kib/kb/mib/mb/… 等大小写不敏感(解析时统一 ToLower)的单位名映射成常量,还额外兼容了无全名的短形式(如 kkimmi)。

反向解析 ParseBytes

格式化是单向的,而 ParseBytes 负责把“人话字符串”解析回字节数:

humanize.ParseBytes("42 MB")  // => 42000000, nil
humanize.ParseBytes("42 mib") // => 44040192, nil

实现逻辑为:先从左往右扫描数字/小数点/逗号,把数字部分与单位部分切开(数字中的逗号会被剔除),数字用 ParseFloat 解析后乘以查表得到的单位常量;若结果溢出 uint64 或单位无法识别,则分别返回 "too large: ...""unhandled size name: ..." 错误。

相对时间:把 time.Time 翻译成“多久以前/以后”

相对时间功能让 time.Time 直接说出“人话”——12 seconds ago3 days from now

fmt.Printf("This was touched %s.", humanize.Time(someTimeInstance)) // This was touched 7 hours ago.

入口 Time(then) 等价于 RelTime(then, time.Now(), "ago", "from now"),即过去的时间自动带上 “ago”、未来的时间自动带上 “from now”。更通用的写法允许自定义任意时间点与标签:

// a 早于 b 时用 albl(这里是 "earlier"),a 晚于 b 时用 blbl(这里是 "later")
humanize.RelTime(timeInPast, timeInFuture, "earlier", "later") // => 3 weeks earlier

自动切档的量级表

times.go 中的 defaultMagnitudes 是核心:它按时间差从小到大排布了 16 个档位,从 <1s → now2s → 1 second %s1min → %d seconds %s,一路经过分钟、小时、天、周、月、年,直到 LongTime37 * Year)之后统一显示 a long while %s。每个档位由 RelTimeMagnitude 三元组构成:

  • D:该档位的起算时长;
  • DivBy:真实差值除以它得到显示的数量(例如档位 D 为 2 分钟、显示 %d minutes 时 DivBy 取 time.Minute);
  • Format:可含 %d(数量)与 %sago/from now 标签)两个占位符。

因此输出会随时间的推移自动进位换单位:7 小时会显示 7 hours ago,而不是 420 minutes ago

自定义量级:CustomRelTime

如果默认档位不符合业务(比如想以“小时”为最小粒度,或为某个具体时长定制措辞),可自行构造升序排列的 []RelTimeMagnitude 交给 CustomRelTime,即可获得完全自主的相对时间文案。这正是 go-humanize 在“格式化”之外给出的扩展点——连特殊情形的 1 second/1 minute/1 hour/1 day/1 month(单数时省略数量词)都由量级表文案负责。

序数词:0th、1st、2nd、3rd

邮件列表里“想要正确标注序数”的需求催生了 Ordinal

fmt.Printf("You're my %s best friend.", humanize.Ordinal(193)) // You are my 193rd best friend.

对应规则一览(注意 11/12/13 的特例):

输入 输出
0 0th
1 1st
2 2nd
3 3rd
4 4th
11 / 12 / 13 11th / 12th / 13th
21 / 22 / 23 21st / 22nd / 23rd
193 193rd

实现上先默认 th 后缀,再对个位判断 st/nd/rd,唯一的防呆逻辑是:当 x%100 落在 11、12、13 时必须维持 th(否则 11 会被错误写成 11st)。

千分位逗号:Comma / Commaf / BigComma

“往数字里塞逗号”看似简单,边界却很多:

humanize.Comma(0)         // 0
humanize.Comma(100)       // 100
humanize.Comma(1000)      // 1,000
humanize.Comma(1000000000) // 1,000,000,000
humanize.Comma(-100000)   // -100,000
fmt.Printf("You owe $%s.\n", humanize.Comma(6582491)) // You owe $6,582,491.

comma.go 提供了三个函数覆盖不同数据宽度:

函数 输入 示例 特点
Comma(v int64) 有符号 64 位整数 834142 → 834,142 特判 math.MinInt64(绝对值无法直接取负),硬编码返回 -9,223,372,036,854,775,808
Commaf(v float64) 浮点数 834142.32 → 834,142.32 FormatFloat(...,'f',-1,64) 最短表示后,整数部分自右向左每三位补逗号,小数部分原样保留
BigComma(b *big.Int) 任意大整数 基于 big.Int 循环 DivMod 1000 分组,宽度不受 int64 限制

配套的 CommafWithDigits(f, decimals) 可截断小数位(注意是截断而非四舍五入),例如 CommafWithDigits(834142.32, 1) → 834,142.3

Ftoa:去掉多余尾零的浮点字符串

Go 标准库 %f 默认固定 6 位小数,打印 2.0 会得到 2.000000。go-humanize 的 Ftoa 通过“先按 'f', 6 格式化,再从字符串尾部剥掉连续 0 与多余小数点”的办法,输出整洁的十进制表示:

fmt.Printf("%f", 2.24)                // 2.240000
fmt.Printf("%s", humanize.Ftoa(2.24)) // 2.24
fmt.Printf("%f", 2.0)                 // 2.000000
fmt.Printf("%s", humanize.Ftoa(2.0))  // 2

若需要限定小数位数,可使用 FtoaWithDigits(num, digits),先截断再剥尾零(例如限制 2 位且去零)。值得注意:这里的去尾零是字符串级裁剪,不引入 math.Round 的浮点误差,这也是它与传统 strconv 用法互补的价值所在。

SI 科学前缀:q → Q 的完整标度

对于电子、物理量纲等场景,SI 前缀格式化让数字自动匹配恰当量级:

humanize.SI(0.00000000223, "M") // 2.23 nM
humanize.SI(1000000, "B")       // 1 MB

前置函数 ComputeSI(input) 返回归一化后的数值与对应前缀(si.go):它取输入绝对值的常用对数并按 3 的倍数向下取整指数,把数值缩放进 [1, 1000) 区间,同时处理了一个特例——若归一化后恰为 1000.0,则进位一格(输出 1 M 而非 1000 k)。SI(input, unit) 再叠加上调用方提供的单位字符串,并通过 Ftoa 保证不留尾零。

解析侧 ParseSI 能把带前缀的字符串还原:

humanize.ParseSI("2.2345 pF") // => (2.2345e-12, "F", nil)

内置前缀表覆盖 q(quecto,1e-30)Q(quetta,1e30) 共 21 档(含正负幂),代码逐行注释了每个缩写对应的词源(micro 用 µ)。数值部分支持可选的 +/- 号与千分位小数,且数字与字母间允许空格。

超大字节与 big.Int 支持

当数值超出 uint64 时,bigbytes.gomath/big 给出了无损版本:BigBytes/BigIBytes 分别输出 SI 与 IEC 格式,容量一路延伸到 QB/QiB1e30 / 1024^10);ParseBigBytes"42 MB" 这类字符串解析成 *big.Int,过程中借助 big.Rat 完成任意精度小数乘法再取整,避免中间溢出。对于记账、存储系统配额等对精度敏感的场景,应优先选用这一组 API,而不是把值硬塞进 uint64

英文专用函数(上游 english 子包)

按 README 的说明,以下函数位于 go-humanize 的 humanize/english 子包,面向英文文案的本地化写作(在本仓库的 vendor 快照中未包含该子包,使用前需引入上游完整包):

英文复数(PluralWord 只返回单词,Plural 附带数量前缀):

english.PluralWord(1, "object", "")   // object
english.PluralWord(42, "object", "")  // objects
english.PluralWord(2, "bus", "")      // buses
english.PluralWord(99, "locus", "loci") // loci

english.Plural(1, "object", "")   // 1 object
english.Plural(42, "object", "")  // 42 objects
english.Plural(2, "bus", "")      // 2 buses
english.Plural(99, "locus", "loci") // 99 loci

第三个参数允许显式传入不规则复数形式(如 locus → loci),未提供时走常规加 s/es 的规则。

词语串联(WordSeries 用逗号与连词组织列表,OxfordWordSeries 额外输出牛津逗号):

english.WordSeries([]string{"foo"}, "and")            // foo
english.WordSeries([]string{"foo", "bar"}, "and")     // foo and bar
english.WordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar and baz

english.OxfordWordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar, and baz

实践建议小结

围绕 README 与源码,最后给出几条可落地的选择标准,方便在真实项目中快速决策:

  1. 容量文案选进制:操作系统/内存、文件系统等二进制场景选 IBytes(MiB 基 1024);带宽、磁盘标称等十进制营销规格选 Bytes(MB 基 1000),二者单位写法不同,切勿混用(详见 bytes.go)。
  2. 相对时间要自定义粒度:默认量级表粒度到秒;若日志、监控只需分钟级,请基于 CustomRelTimeRelTimeMagnitude 自建档位(times.go)。
  3. 大数不截断:涉及超过 int64/uint64 的字节量或金额,一律走 BigBytes/BigComma 系列。
  4. 反向解析同样重要:格式化与解析(ParseBytes/ParseBigBytes/ParseSI)成对设计,适合做配置回读、校验或数据入库前的归一化。

对于在容器生态里需要把磁盘用量、镜像大小、日志时间等底层指标呈现给终端用户的 Go 服务,这套 vendored 实现提供了“格式化—展示—再解析”的完整闭环,值得直接复用。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388