必须用 decimal128 保证金融计算精度,且插入需显式调用 fromstring、查询需 tostring 处理、聚合用 $todecimal、跨语言传字符串、中间计算须用 decimal 库。

MongoDB 默认把数字当 double 存,但做金融、计费、税率等场景时,double 的舍入误差会直接导致金额对不上——这不是精度“差不多”的问题,而是“100.01 × 100 = 10000.999999999998”这种肉眼可见的错。必须用 Decimal128,且不能只靠插入时写对类型,整个链路(插入、查询、聚合、驱动解析)都得对齐。
插入时必须显式构造 Decimal128,不能靠自动识别
很多人以为只要传个字符串或高精度数字,MongoDB 就会自动存成 Decimal128,其实不会:double 字面量(如 123.45)、Number 实例、甚至 "123.45" 字符串(没包装)全都会被转成 double。
- Node.js 驱动中,必须用
Decimal128.fromString("123.45")或Decimal128.fromString(123.45.toString())—— 直接传123.45不行 - 避免用
parseFloat("123.45")再包Decimal128,因为parseFloat本身已损失精度 - 如果数据来自用户输入或 CSV,务必先校验格式(如正则
^-?\d+(\.\d{1,2})?$),再转字符串进fromString
查询结果里 Decimal128 是包装对象,不是原始数字
查出来字段值长这样:{ "$numberDecimal": "123.45" }(BSON 序列化格式),或者在 Node.js 里是 Decimal128 实例。它既不是 number,也不能直接参与算术运算。
- 要取原始数值,用
.toString()得到字符串,再按需转parseFloat(仅用于展示)或保持字符串做后续计算(推荐) - 别写
doc.price.$numberDecimal—— 这是旧版驱动或错误解析 JSON 的写法;现代驱动返回的是Decimal128实例,有.toString()、.toHexString()等方法 - 用 Mongoose 时,需在 Schema 中显式声明
{ type: mongoose.Types.Decimal128 },否则默认当double解析,$numberDecimal字段会被丢弃或报错
聚合阶段要用 $toDecimal 而不是 $convert 或强制类型转换
在 $group、$sum 或字段计算中,如果源字段是 double,直接 $sum 会延续浮点误差;想中途转精度,必须用聚合操作符。
- 正确写法:
{ $sum: { $toDecimal: "$amount" } }——$toDecimal是专为 Decimal128 设计的,支持double、string、int等输入 - 别用
{ $convert: { input: "$amount", to: "decimal" } },虽然语义等价,但$toDecimal更简洁、更易读、错误提示更明确 -
$toDecimal遇到无法解析的值(如null、空字符串、非数字字符串)会返回null,不是报错,所以聚合前最好加$match过滤掉脏数据
跨语言/驱动时 Decimal128 的序列化行为不一致
Go 驱动里 Decimal128 是 {h uint64, l uint64} 结构体;Python PyMongo 返回 Decimal128 对象,有 .to_decimal() 方法;而 JavaScript 驱动只提供 .toString() 和 .valueOf()(后者仍返回对象)。导出为 JSON 时尤其危险。
- 导出数据前,统一用
.toString()转成字符串,不要依赖JSON.stringify自动处理 —— 它可能把Decimal128序列化为空对象或抛错 - 不同语言间传递 Decimal128 值,约定用字符串传输(如
"123.4500"),接收方再各自构造,避免二进制表示差异 - Compass 或 mongosh 里看到的
$numberDecimal是 BSON-to-JSON 扩展语法,不是标准 JSON,其他系统(如 Kafka、HTTP API)通常不认,必须提前解包
最常被忽略的一点:Decimal128 不是“开了开关就全程高精度”,它只保证存储和 BSON 层面的精度。一旦你用 JS 的 +、* 对它 .toString() 后的结果做运算,又回到 double 世界。真要端到端精确,所有中间计算也得用 decimal 库(如 Python 的 decimal、JS 的 decimal.js)完成。











