Decimal128是金融场景唯一可靠选择,因其以十进制精确存储避免IEEE 754二进制浮点误差(如0.1+0.2≠0.3),而double等类型因精度丢失必然导致对账偏差;正确使用需全程字符串构造、显式类型声明及全链路十进制传递。

Decimal128 是唯一可靠的选择,其他类型在金融场景下都会出错。
为什么不能用 double
因为 double 是 IEEE 754 二进制浮点数,0.1 + 0.2 ≠ 0.3,100.01 × 100 会变成 10000.999999999998。这不是“偶尔偏差”,而是每次计算都可能累积误差——线上对账差 1 分钱,根本没法解释。
常见错误现象:
- 插入
{ price: 19.99 },查出来是19.990000000000002 - 聚合
$sum多笔金额后,和 Excel 手动加总不一致 - 用
parseFloat解析用户输入再存入,精度已在第一步丢失
Decimal128 的正确写法(Node.js 驱动)
必须显式构造,不能依赖自动转换:
- ✅ 正确:
Decimal128.fromString("19.99")或Decimal128.fromString((19.99).toString()) - ❌ 错误:
Decimal128.fromString(19.99)(传 number 字面量,已失真) - ❌ 错误:
new Decimal128(19.99)(驱动不支持该构造方式) - ⚠️ 注意:字符串必须符合格式,如
"-?\d+(\.\d{1,2})?",避免"19.990"或空格导致$toDecimal返回null
Mongoose 中容易漏掉的配置
如果不声明 Schema 类型,Mongoose 默认把字段当 double 解析,$numberDecimal 字段会被丢弃或报错:
- ✅ 必须写:
price: { type: mongoose.Types.Decimal128 } - ❌ 不要写:
price: Number或price: { type: String } - 查询后取值必须用
.toString(),不是.valueOf()或直接参与+运算 - 聚合时别用
$convert,改用$toDecimal,例如:{ $sum: { $toDecimal: "$price" } }
跨语言协作的关键约束
Decimal128 在 BSON 层是 {"$numberDecimal": "19.99"},但不同语言驱动解析行为不同:
- Go 驱动默认不启用
Decimal128支持,需显式注册解码器或用primitive.Decimal128接收 - Java 驱动需用
Decimal128.parse("19.99")构造,且 ORM 框架(如 Spring Data MongoDB)要确认版本 ≥ 3.0 - 所有中间服务(比如 Node.js → Kafka → Go)必须传字符串,不能转成 float/double 再传
最常被忽略的一点:即使数据库存对了,只要任意一环用 parseFloat、float64、Double 接收或参与运算,精度就彻底丢了——Decimal128 的价值只在线路全链路对齐时才成立。


















