应使用 int64 整数运算计算压缩比,避免 float64 因 ieee-754 限制导致的精度失真;需高精度时用 math/big.rat,禁用 decimal 等过度设计方案。

float64 本身不能精确表示大多数十进制小数,所以直接用 float64 计算压缩比(如 originalSize / compressedSize)会引入不可控误差,尤其当原始数据量大、压缩率高或需横向对比时,微小偏差可能掩盖真实算法优劣。这不是 Go 特有,而是 IEEE-754 的固有限制。
你真正需要的不是“浮点数精度”,而是压缩比这个比值本身的数值可信度——它必须可复现、可比较、不随输入微小变化而跳变。
为什么 float64 算压缩比会失真?
假设原始大小是 8192 字节(1024 个 float64),压缩后为 2048 字节。理想压缩比是 4.0。但若你误把字节数读成 float64 再除:var orig, comp float64 = 8192.0, 2048.0ratio := orig / comp
表面看没问题,可一旦原始数据含非整数字节(比如从网络流读取、或经内存对齐填充),orig 可能是 8192.000000000001 或 8191.999999999999 —— 这些值在 float64 中无法精确存储,除法结果就不再是严格 4.0,而是 4.000000000000001 或 3.9999999999999996。
这种失真在做多算法横向对比(如 MLF vs ZSTD)时,会让本应相同的压缩率显示为“MLF 略优”,误导判断。
用 int64 做整数除法再补小数位
压缩比本质是两个整数量纲一致的比值(字节/字节),全程用整数运算最可靠:
– 把原始大小和压缩后大小都存为 int64
– 需要保留 3 位小数时,先放大 1000 倍再整除:ratioFixed := (orig * 1000) / comp
– 结果就是整数 4000,打印时写成 fmt.Printf("%.3f", float64(ratioFixed)/1000) 或直接拼字符串 fmt.Sprintf("%d.%03d", ratioFixed/1000, ratioFixed%1000)
这样既避免浮点中间计算,又保证输出稳定。注意:整数除法会向下取整,若需四舍五入,改用 ((orig * 1000) + comp/2) / comp。
需要动态精度或大数时用 math/big.Rat
当压缩前后字节数极大(> 2⁶³)、或需保留任意精度(如压测中对比 1.0000001 和 1.0000002 的差异),int64 会溢出。math/big.Rat 是 Go 标准库中唯一能精确表示有理数的类型:
– 初始化:r := new(big.Rat).SetFrac(big.NewInt(orig), big.NewInt(comp))
– 转为带指定小数位的 float64:r.Float64()(仅用于显示,不参与计算)
– 或转为字符串:r.FloatString(6)(精确到小数点后 6 位,无舍入误差)
关键点:Rat 不做浮点运算,它存的是分子/分母两个整数,所有操作都是精确有理数运算。但别用它做高频循环计算——分配开销明显高于 int64。
别碰 github.com/shopspring/decimal 算压缩比
这个库专为金融场景设计,内部用十进制字符串模拟高精度,性能比 int64 低 100 倍以上,且对纯整数比值属于杀鸡用牛刀。
常见误用:
– 用 decimal.NewFromFloat64(float64(orig)).Div(decimal.NewFromFloat64(float64(comp)))
这一步就把 orig 和 comp 先转成失真的 float64,再喂给 decimal,等于从源头污染数据。
– 正确做法是:用 decimal.NewFromString(strconv.FormatInt(orig, 10)) 构造,但此时已不如直接用 big.Rat 简洁。
压缩比不是科学计算,它是工程度量指标。只要原始字节数和压缩后字节数是整数,就永远优先走 int64 整数路径;只有当字节数本身不确定(比如流式压缩未结束就估算)或需超长精度时,才升级到 big.Rat。任何把字节数先塞进 float64 的做法,都在自废精度根基。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











