Go 人性化数字格式化完整指南:go-humanize 的尺寸、时间、序数与 SI 单位格式化实战
2026/9/17 17:05:00 网站建设 项目流程

Go 人性化数字格式化完整指南:go-humanize 的尺寸、时间、序数与 SI 单位格式化实战

【免费下载链接】inngestThe leading workflow orchestration platform. Run stateful step functions and AI workflows on serverless, servers, or the edge.项目地址: https://gitcode.com/GitHub_Trending/in/inngest

go-humanize是一套把"枯燥丑陋的数字"转换为"人友好字符串"的 Go 工具库:它能将82854982变成83 MB,将time.Time输出为3 days ago,还能处理序数词、千分位逗号、浮点去尾零以及 SI 科学计数法。本文以仓库内 vendored 的 README.markdown 为主体骨架,结合其位于 vendor/github.com/dustin/go-humanize 下的完整源码实现(当前仓库在 go.mod 中声明依赖github.com/dustin/go-humanize v1.0.1),逐函数讲解 API 用法、底层算法与边界行为,让读者既能直接上手,也能理解每个格式化函数背后的实现原理。

安装与引入

README 给出的引入方式非常直接:使用go get拉取依赖,导入路径为github.com/dustin/go-humanize,包名(use it as)为humanize

import "github.com/dustin/go-humanize"

在 go.mod 中可以看到,当前仓库将其以v1.0.1版本记录为 indirect 依赖,并在vendor/github.com/dustin/go-humanize/目录下完整保留了该库的源码(bytes.gotimes.gocomma.gosi.goordinals.goftoa.gobig.gobigbytes.gonumber.go等)。下面按功能模块逐一展开。

字节大小格式化(Sizes)

这是 go-humanize 最常用的功能:把数字字节数转换为可读的容量字符串,例如82854982可输出为83 MB(SI 十进制)或79 MiB(IEC 二进制),完全由你选择。

fmt.Printf("That file is %s.", humanize.Bytes(82854982)) // That file is 83 MB.

从 bytes.go 的实现可以看到两个核心函数:

  • Bytes(s uint64) string(bytes.go):以 1000 为底数,后缀序列为B, kB, MB, GB, TB, PB, EB,输出十进制(SI)容量。
  • IBytes(s uint64) string(bytes.go):以 1024 为底数,后缀序列为B, KiB, MiB, GiB, TiB, PiB, EiB,输出二进制(IEC)容量。

两者的底层都调用同一个私有函数humanateBytes(bytes.go),其算法要点如下:

  1. 小于 10 字节时直接输出"%d B",不做单位换算;
  2. 使用logn(bytes.go)即math.Log(n) / math.Log(b)计算以 base 为底的对数,取math.Floor得到数量级指数e
  3. 数值按math.Floor(float64(s)/math.Pow(base, e)*10+0.5)/10保留一位小数(四舍五入);
  4. 若结果小于 10 用"%.1f %s",否则用"%.0f %s"抹掉小数。

单位常量与解析

源码中还定义了两组可直接引用的常量:

  • IEC 常量(bytes.go):Byte = 1 << (iota * 10),即KiByte(1024)、MiByte(1<<20)、GiByteTiBytePiByteEiByte
  • SI 常量(bytes.go):IByte = 1KByte = 1000MByteGByteTBytePByteEByte,按千进位。

与之配套的解析函数ParseBytes(s string) (uint64, error)(bytes.go)可以把字符串反解回字节数,例如"42 MB" -> 42000000"42 mib" -> 44040192。它内部通过bytesSizeTable(bytes.go)查表,该表同时收录了带完整后缀(b/kib/kb/...)和省略后缀(ki/k/m/...)的写法,且解析时大小写不敏感;如果数字部分含逗号会先剥离逗号,遇到超出uint64上限的数值会返回too large错误,未知单位则返回unhandled size name错误。

相对时间格式化(Times)

humanize.Time可以把一个time.Time转成相对时间表述,例如12 seconds ago3 days from now

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

README 提到,该时间实现源自 Kyle Lemons 在某次 IRC 交流中的想法。从 times.go 可以看到完整的调用链:

  • Time(then time.Time) string(times.go)是对RelTime(then, time.Now(), "ago", "from now")的封装;
  • RelTime(a, b time.Time, albl, blbl string) string(times.go)提供自定义时间点与标签的能力,如RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier"
  • CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnitude) string(times.go)允许你完全自定义"量级切换表"。

量级表与二分查找

RelTimeMagnitude(times.go)是一个包含D(触发阈值时长)、Format(格式串,含%s标签位与%d数量位)、DivBy(换算除数)三个字段的结构体。默认量级表defaultMagnitudes(times.go)按时间从短到长排列,覆盖now1 second ago%d seconds ago1 minute ago直到%d years ago和最终的a long while ago

格式化时,CustomRelTime先根据a.After(b)判断取albl(过去,ago)还是blbl(未来,from now),然后用sort.Search对按D升序排列的量级表做二分查找,找到第一个D > diff的档位,最后解析 Format 中的%s%d占位符填充标签与数量。时间单位常量(times.go)中Month = 30 * DayYear = 12 * MonthLongTime = 37 * Year,即"一个月约 30 天、一年约 360 天",这是相对时间表述的近似约定,而非日历计算。

序数词格式化(Ordinals)

序数词功能源自一次 golang-nuts 邮件列表讨论(用户希望输出排名后缀)。其转换规则为:

0 -> 0th 1 -> 1st 2 -> 2nd 3 -> 3rd 4 -> 4th [...]
fmt.Printf("You're my %s best friend.", humanize.Ordinal(193)) // You are my 193rd best friend.

实现位于 ordinals.go:默认后缀为th,仅当个位为 1/2/3 且整个数字不以 11/12/13 结尾时才分别替换为st/nd/rd,从而正确处理11th12th13th这些特例。该函数接收int并返回字符串,非负数直接拼接,负数也会得到-1st之类的结果。

千分位逗号格式化(Commas)

"想把逗号塞进数字里?请便。" README 给出的行为示例:

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

从 comma.go 可以读到三个层次的实现:

  • Comma(v int64) string(comma.go):对int64每三位插入逗号。一个值得注意的边界处理是math.MinInt64无法直接取反,因此被特判返回-9,223,372,036,854,775,808(comma.go);负数先记录符号再对绝对值分组,每组不足三位时用0左补齐;
  • Commaf(v float64) string(comma.go):浮点版本,如Commaf(834142.32) -> 834,142.32,小数部分原样保留;
  • CommafWithDigits(f float64, decimals int) string(comma.go):限制小数位数,如CommafWithDigits(834142.32, 1) -> 834,142.3
  • BigComma(b *big.Int) string(comma.go):为math/big的大整数提供同样的千分位格式,配合 big.go 中的oom数量级计算,可格式化超出int64范围的超大数。

浮点去尾零格式化(Ftoa)

标准库的%f会输出固定小数位,导致2.24变成2.240000Ftoa提供"去除尾部零"的更好看的浮点格式化:

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

实现位于 ftoa.go:先用strconv.FormatFloat(num, 'f', 6, 64)固定输出 6 位小数,再由stripTrailingZeros(ftoa.go)从尾部向前裁剪多余的0,遇到小数点本身也一并去掉,因此2.0输出为2。配套的FtoaWithDigits(num, digits)(ftoa.go)先裁剪到指定小数位再去除尾零。

SI 科学计数法格式化(SI notation)

SI 模块使用 SI 前缀(metric prefix) 格式化任意数量级的数字,例如:

humanize.SI(0.00000000223, "M") // 2.23 nM

在 si.go 中,前缀表siPrefixTable(si.go)覆盖从q(quecto,10⁻³⁰)到Q(quetta,10³⁰)的全部 21 个 SI 前缀,含µ(micro)这样的 Unicode 字符。核心函数:

  • ComputeSI(input float64) (float64, string)(si.go):计算最合适的指数,指数按math.Floor(exponent/3)*3对齐到三位一组;还处理了value == 1000.0时"应返回 1 M 而不是 1000 k"的特例;
  • SI(input float64, unit string) string(si.go):使用Ftoa格式化数值并拼接前缀与单位,如SI(1000000, "B") -> 1 MBSI(2.2345e-12, "F") -> 2.2345 pF
  • SIWithDigits(input, decimals, unit)(si.go):限制小数位,如SIWithDigits(1000000, 0, "B") -> 1 MB
  • ParseSI(input string) (float64, string, error)(si.go):反向解析,如ParseSI("2.2345 pF") -> (2.2345e-12, "F", nil)。其解析正则riParseRegex(si.go)在init()中根据前缀表动态拼装而成。

大数容量格式化(BigBytes 系列)

README 未单独介绍、但同仓库内一并提供的能力是math/big大数版本。在 bigbytes.go 中:

  • BigBytes(s *big.Int) string(bigbytes.go)与BigIBytes(s *big.Int) string(bigbytes.go)分别以 1000/1024 为底,后缀扩展到了ZB, YB, RB, QB(对应 IEC 的ZiB, YiB, RiB, QiB),可格式化任意大的字节数;
  • 常量BigKiByteBigQiByte(bigbytes.go)通过big.Int乘法逐级构建;
  • ParseBigBytes(bigbytes.go)用big.Rat做有理数运算后整除得到字节数,避免浮点精度损失。

其格式化辅助函数humanateBigBytes(bigbytes.go)依赖 big.go 中的oomm/oom(big.go)做基于big.Int的除模迭代来计算数量级。

自定义模板格式化(FormatFloat / FormatInteger)

number.go 提供了另一套按模板字符串渲染数字的能力(改编自 gorhill 的 gist 实现)。FormatFloat(format string, n float64) string(number.go)使用#,###.##这类格式串控制千分位分隔符、小数分隔符与精度,例如(n = 12345.6789):

"#,###.##" => "12,345.67" "#,###." => "12,345" "#,###" => "12345,678" ""(默认格式) => "12,345.67"

该函数最高支持 9 位小数精度,且对NaN+Infinity-Infinity做了特判处理;FormatInteger(format string, n int) string(number.go)则是面向模板调用的整数便捷版本。

英语专属函数(humanize/english 子包)

README 说明以下函数位于humanize/english子包中,即导入路径为github.com/dustin/go-humanize/english

复数化(Plurals)

简单的英语复数规则:

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

Plural输出"数字 + 单词"组合,PluralWord只输出单词本身;两个函数都接受第三个参数作为不规则复数形式(如locusloci),为空时按规则变化(如bus -> buses)。

词串连接(Word series)

把逗号分隔的单词列表用连词连接成通顺的英文短语:

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

WordSeries按"a, b and c"形式连接,OxfordWordSeries则额外在最后一项前保留逗号(牛津逗号风格)。

边界情况与使用注意事项

综合上述源码,使用 go-humanize 时有几个值得留意的点:

  1. 单位进制要选对Bytes走 1000(SI),IBytes走 1024(IEC)。存储厂商与操作系统的口径常不一致,展示容量时务必与业务口径统一;
  2. 相对时间只是近似Month/Year分别按 30 天、360 天折算,不涉及日历与时区,跨月/跨年场景请自行评估是否符合预期;
  3. 序数词特例:11/12/13 结尾的数字后缀永远是th11th12th13th),Ordinal已正确处理;
  4. Commaint64下界math.MinInt64被特判为-9,223,372,036,854,775,808,其余负数先取绝对值再分组;
  5. 解析函数会返回错误ParseBytes对未知单位返回unhandled size name,对超出uint64的值返回too large,使用时应处理 error;
  6. Ftoa固定走 6 位小数:先FormatFloat(f, 6)再裁剪尾零,超过 6 位的小数位会被截断,需要更多精度时可用FtoaWithDigits或直接使用strconv

小结

go-humanize 是一个小而精的格式化工具库:字节容量(SI/IEC 双进制)、相对时间、序数词、千分位逗号、浮点去尾零、SI 前缀以及大数版本一应俱全,并附带反向解析函数与自定义量级表/格式模板。本文结合仓库中 vendor/github.com/dustin/go-humanize 的完整源码(当前依赖版本见 go.mod)逐一剖析了各 API 的调用关系与边界处理,读者可以在 README.markdown 与上述各.go文件中继续深入研读原始实现与注释。

【免费下载链接】inngestThe leading workflow orchestration platform. Run stateful step functions and AI workflows on serverless, servers, or the edge.项目地址: https://gitcode.com/GitHub_Trending/in/inngest

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询