最佳实践与常见坑
最佳实践
1. 传入字符串,而不是数字
超过 15 位有效数字、来自浮点运算结果、或要求精确的值,一律用字符串:
js
// ❌ 数字字面量已被 JS 舍入
new Decimal(0.7 + 0.1) // '0.7999999999999999'
new Decimal(88259496234518.57) // '88259496234518.56'
// ✅ 字符串精确解析
new Decimal('0.7999999999999999') // 原样保留
new Decimal('88259496234518.57') // '88259496234518.57'2. 为业务选好精度,而不是默认值
默认 20 位有效数字对展示足够,但对「金额 × 数量」这类链式运算,中间结果会被舍入,累积误差:
js
Decimal.set({ precision: 20 });
new Decimal('0.1').plus('0.2').times(3) // '0.90000000000000000000'?不——
// 实际:'0.9'(0.3 × 3 = 0.9,在 20 位精度内精确)真正需要小心的是除法后继续运算。金融场景推荐:精度设 30~50,展示时再 toFixed(2):
js
Decimal.set({ precision: 30, rounding: Decimal.ROUND_HALF_UP });
// ... 运算 ...
result.toFixed(2) // 展示时才舍入到分TIP
规则:内部运算用高精度,对外展示才舍入。
3. 需要多种精度时用 clone,别改全局
js
const Money = Decimal.clone({ precision: 30, rounding: 4 });
const Stats = Decimal.clone({ precision: 100, rounding: 6 });
new Money('1').div(3).toString() // '0.333333333333333333333333333333'
new Stats('1').div(3).toString() // 100 位
Decimal.precision // 20,全局不受影响4. 用静态方法做纯函数式调用
不需要实例时(如对输入值做一次运算),静态方法更简洁:
js
Decimal.add(a, b) // 等价于 new Decimal(a).plus(b)
Decimal.max(...prices)
Decimal.sum(...items)5. 序列化:依赖 toJSON
JSON.stringify 自动调用 toJSON(),输出字符串,往返无损:
js
const obj = { price: new Decimal('19.99') };
const json = JSON.stringify(obj); // '{"price":"19.99"}'
const back = JSON.parse(json);
new Decimal(back.price).eq('19.99') // true6. 金额计算完整范例
js
const Decimal = require('decimal.js');
Decimal.set({ precision: 30, rounding: Decimal.ROUND_HALF_UP });
// 订单:3 件单价 0.10 的商品 + 1 件单价 19.90 的商品,9 折,运费 5.00
const subtotal = Decimal.sum('0.10', '0.10', '0.10', '19.90'); // '20.20'
const discounted = subtotal.times('0.9').toDP(2); // '18.18'
const total = discounted.plus('5.00').toDP(2); // '23.18'
total.toFixed(2) // '23.18'(展示)常见坑清单
坑 1:0.1 + 0.2 不等于 0.3
js
new Decimal('0.1').plus('0.2').eq('0.3') // true(用字符串就没事)坑 2:数字字面量传参丢精度
js
new Decimal(1.0000000000000001) // '1'见「最佳实践 1」。
坑 3:toFixed 与 toDP 分不清
toFixed→ string,补零,用于展示。toDP→ Decimal,不补零,用于继续运算。
js
new Decimal('1.2').toFixed(2) // '1.20'
new Decimal('1.2').toDP(2) // '1.2'坑 4:round() 没有参数
js
new Decimal('2.5').round(0, Decimal.ROUND_DOWN) // '3'!参数被忽略,用的是全局 rounding
new Decimal('2.5').toDP(0, Decimal.ROUND_DOWN) // '2' ✅ 要这样指定模式坑 5:用 == / < 比较 Decimal
js
new Decimal(1) === new Decimal(1) // false(引用比较)
new Decimal(2) < new Decimal(10) // false(valueOf 返回字符串,按字典序比)一律用 .eq() / .lt() / .gt() / .cmp()。
坑 6:max / min / sum 不接受数组
js
Decimal.max([1, 2, 3]) // 抛错!要用展开:Decimal.max(...[1, 2, 3])坑 7:-0 的符号
js
new Decimal('-0').isNegative() // true
new Decimal('-0').toString() // '0'
new Decimal('-0').valueOf() // '-0'
new Decimal(0).isPositive() // true(0 被视为正)坑 8:toDP / toSD 不接受负数
js
new Decimal('1234.5').toDP(-1) // 抛错(decimal.js 不支持负小数位)坑 9:超出 Number 范围的输入直接溢出
js
new Decimal(2e+308) // 'Infinity'
new Decimal(1e-324) // '0'大数请用字符串。
坑 10:把 Decimal 当普通对象参与算术运算
js
new Decimal('1.5') + 1 // '1.51'(valueOf 返回字符串 → 拼接!)
new Decimal('1.5') * 2 // 3(会被隐式转 number,但可能丢精度)用方法:.plus(1)。
坑 11:修改全局配置影响所有代码
第三方库也在用 decimal.js 时,Decimal.set() 是进程级全局。要么用 clone(),要么用后立即恢复:
js
const old = { precision: Decimal.precision, rounding: Decimal.rounding };
Decimal.set({ precision: 50 });
try { /* 你的运算 */ } finally { Decimal.set(old); }错误处理
所有错误都是普通 Error,消息以 [DecimalError] 开头:
js
try {
new Decimal('not a number');
} catch (e) {
e.message // '[DecimalError] Invalid argument: not a number'
}常见错误来源:非法输入、precision 超范围、crypto 不可用、toDP 负数。
与同类库对比速查
| 对比项 | decimal.js | bignumber.js | big.js |
|---|---|---|---|
| 精度单位 | 有效数字 | 小数位 | 小数位 |
| 所有运算都舍入 | ✅ | ❌ | ❌ |
| 三角函数/对数/指数 | ✅ | ❌ | ❌ |
| 非整数幂 | ✅ | ❌ | ❌ |
| 二进制/八进制/十六进制 | ✅ | 部分 | ❌ |
| 文件大小 | 大(~90KB) | 中 | 小(~7KB) |
| 典型用途 | 需要数学函数的高精度计算 | 通用高精度 | 最小化依赖 |
选型建议:
- 需要
sin/cos/ln/exp或非整数幂 → decimal.js - 只需要四则运算 + 小数位控制 → bignumber.js / big.js
- 只想修
0.1+0.2,文件越小越好 → big.js - 不需要三角函数的 decimal.js 轻量版 → decimal.js-light
