$ multiply无法解决聚合精度丢失,因其仅放大已有IEEE 754误差;唯一保精方案是入库即用Decimal128,全程避免浮点运算,聚合中禁用数值操作符,改用字符串模拟计算。

直接用 $multiply 放大浮点数再取整,不能解决聚合阶段的精度丢失问题——它只是把误差一起放大了。
为什么 $multiply 放大倍数在聚合里无效
MongoDB 聚合管道中所有数字运算($multiply、$add、$divide)默认都在 double 精度下执行。哪怕你写 {$multiply: [{ $toDouble: "$value" }, 100]},输入值本身可能已经是 0.1 + 0.2 = 0.30000000000000004 这种 IEEE 754 表示,放大后仍是带误差的 double,后续 $toInt 或 $floor 只是截断这个错误值。
- 原始字段存的是
NumberDouble(比如从 JSON 直接插入的0.1),聚合时不会自动转成Decimal128 -
$multiply不接受Decimal128作为操作数 —— 官方文档明确列出其只支持double、int、long,不支持decimal128 - 即使你用
$convert强转为decimal128,后续一旦参与任何算术操作(包括$multiply),MongoDB 会隐式转回 double,丢失精度
聚合中真正能保精度的操作方式
唯一可行路径是:避开所有浮点运算,全程用字符串或整型做“伪计算”。适用于金额、计费、税率等必须精确到小数点后 N 位的场景。
- 入库时就用
Decimal128.fromString("123.45")存,字段类型是Decimal128,不是double - 聚合中禁止出现
$multiply、$add等数值运算符;改用$concat+ 正则提取 + 字符串拼接模拟乘法(例如把"123.45"× 100 变成"12345") - 若必须输出数值,最后用
$convert转long(如单位为“分”),而非double - 注意:
$sum、$avg对Decimal128字段是支持的,但结果仍是Decimal128;只要不落地为 double,精度就还在
容易被忽略的隐式类型转换陷阱
你以为在聚合里写了 { $convert: { input: "$amount", to: "decimal128" } } 就安全了?不一定。
- 如果源字段
$amount本身就是NumberDouble(比如前端传{"amount": 0.1}),$convert只是把那个已失真的 double 套上 decimal128 外壳,内部值仍是0.10000000000000000555... -
$cond或$ifNull中混用double和decimal128,MongoDB 会统一转成double再比较,导致0.1 === 0.1返回false - 使用
$round时,第二个参数(小数位数)若为double,也会触发隐式降级 —— 必须确保它是int字面量,如2,而非2.0
精度问题从来不在“怎么算”,而在于“从哪开始就是准的”。Decimal128 不是银弹,它只在数据写入那一刻起生效;聚合管道里每一步类型判断,都可能悄悄把你拉回 double 的世界。


















