Skip to content

最佳实践与常见坑

最佳实践

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')      // true

6. 金额计算完整范例

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:toFixedtoDP 分不清

  • toFixedstring,补零,用于展示。
  • toDPDecimal,不补零,用于继续运算。
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.jsbignumber.jsbig.js
精度单位有效数字小数位小数位
所有运算都舍入
三角函数/对数/指数
非整数幂
二进制/八进制/十六进制部分
文件大小大(~90KB)小(~7KB)
典型用途需要数学函数的高精度计算通用高精度最小化依赖

选型建议:

  • 需要 sin/cos/ln/exp 或非整数幂 → decimal.js
  • 只需要四则运算 + 小数位控制 → bignumber.js / big.js
  • 只想修 0.1+0.2,文件越小越好 → big.js
  • 不需要三角函数的 decimal.js 轻量版 → decimal.js-light

相关章节