decimal.js vs bignumber.js vs big.js vs mathjs
JavaScript 高精度数值计算库选型指南
decimal.jsbignumber.jsbig.jsmathjs类似的npm包:

JavaScript 高精度数值计算库选型指南

JavaScript 原生的 Number 类型基于 IEEE 754 标准,导致浮点数运算存在精度丢失问题(例如 0.1 + 0.2 !== 0.3)。big.js、bignumber.js、decimal.js 和 mathjs 都是为了解决这一问题而设计的库,但它们的侧重点不同。前三个专注于提供任意精度的十进制算术运算,适合金融、会计等对精度敏感的场景;而 mathjs 是一个功能全面的数学计算引擎,不仅支持高精度数字,还提供矩阵、代数、统计等复杂数学功能。

npm下载趋势

3 年

GitHub Stars 排名

统计详情

npm包名称
下载量
Stars
大小
Issues
发布时间
License
decimal.js65,040,6047,269284 kB271 年前MIT
bignumber.js41,832,4657,012500 kB104 个月前MIT
big.js20,186,7055,20258.9 kB102 年前MIT
mathjs2,427,66015,0959.43 MB2407 个月前Apache-2.0

JavaScript 高精度数值计算库深度对比:big.js vs bignumber.js vs decimal.js vs mathjs

在 JavaScript 中,0.1 + 0.2 !== 0.3 是一个经典问题。这是因为 JS 的 Number 类型基于 IEEE 754 双精度浮点数标准,无法精确表示某些十进制小数。为了解决这个问题,社区涌现了多个高精度计算库。本文将深入对比 big.js、bignumber.js、decimal.js 和 mathjs,帮助你在架构设计中做出正确选择。

🏗️ 核心设计理念:类实例 vs 函数工具

这四个库在 API 设计风格上有显著差异。前三个库(big.js、bignumber.js、decimal.js)主要采用面向对象的设计,通过实例方法链式调用;而 mathjs 则是一个函数式的数学引擎,提供全局工具函数。

big.js 采用极简的类设计,实例化后直接调用方法。

import Big from 'big.js';
const x = new Big(0.1);
const y = new Big(0.2);
const result = x.plus(y); // 返回 Big 实例
console.log(result.toString()); // "0.3"

bignumber.js 同样基于类,但支持更丰富的链式操作和配置。

import BigNumber from 'bignumber.js';
const x = new BigNumber(0.1);
const y = new BigNumber(0.2);
const result = x.plus(y); // 返回 BigNumber 实例
console.log(result.toString()); // "0.3"

decimal.js 的 API 设计与 bignumber.js 非常相似,但在方法命名上略有不同(如 add 而非 plus)。

import Decimal from 'decimal.js';
const x = new Decimal(0.1);
const y = new Decimal(0.2);
const result = x.add(y); // 返回 Decimal 实例
console.log(result.toString()); // "0.3"

mathjs 提供全局命名空间,既可以处理普通数字,也可以配置为处理大数。

import * as math from 'mathjs';
// 默认可能使用普通数字,需显式使用 bignumber 类型或配置
const result = math.add(math.bignumber(0.1), math.bignumber(0.2));
console.log(result.toString()); // "0.3"

⚙️ 精度配置与舍入模式

在金融场景中,舍入模式(如四舍五入、向下取整)至关重要。不同库对配置的管理方式不同。

big.js 通过静态属性配置,全局生效,配置项较少。

import Big from 'big.js';
Big.RM = 1; // 设置舍入模式:向下取整
Big.DP = 2; // 设置小数位数
const result = new Big(1.555).div(1); // 1.55

bignumber.js 提供 config 方法,支持更细粒度的控制,包括指数范围。

import BigNumber from 'bignumber.js';
BigNumber.config({ DECIMAL_PLACES: 2, ROUNDING_MODE: 1 });
const result = new BigNumber(1.555).dividedBy(1); // 1.55

decimal.js 同样使用 config,但在处理极大或极小数值时的行为更加一致。

import Decimal from 'decimal.js';
Decimal.set({ precision: 20, rounding: 1 });
const result = new Decimal(1.555).div(1); // 1.55

mathjs 的配置更为复杂,因为它需要协调整个数学引擎的行为。

import * as math from 'mathjs';
math.config({ precision: 20, number: 'BigNumber' });
const result = math.divide(1.555, 1); // 依赖全局配置

🧮 功能范围:基础算术 vs 全面数学

这是选择 mathjs 与其他三个库的分水岭。如果你只需要加减乘除,前三个库更轻量;如果你需要三角函数、矩阵或表达式解析,mathjs 是唯一选择。

big.js 仅支持基础算术(加减乘除、开方、比较)。

// 不支持三角函数,会报错或无此方法
// new Big(1).sin() // undefined

bignumber.js 支持基础算术和部分高级运算(如幂、绝对值),但不支持三角函数。

const x = new BigNumber(2);
const pow = x.exponentiatedBy(3); // 8
// 不支持 sin, cos 等

decimal.js 在 bignumber.js 基础上扩展了更多数学方法,包括三角函数和对数。

const x = new Decimal(1);
const sinVal = x.sin(); // 支持三角函数
const logVal = x.ln();   // 支持对数

mathjs 提供完整的数学标准库支持,包括矩阵、复数、单位转换等。

const matrix = math.matrix([[1, 2], [3, 4]]);
const det = math.det(matrix); // 计算行列式
const expr = math.evaluate('1.1 + 0.2'); // 解析字符串表达式

📦 工程化考量与维护状态

在大型项目中,库的维护状态和类型支持(TypeScript)是关键决策因素。

  • big.js:维护稳定,更新频率低,因为功能已冻结。类型定义完善。体积最小。
  • bignumber.js:非常活跃,社区生态最好,几乎所有高精度场景的首选。类型定义完善。
  • decimal.js:活跃维护,与 bignumber.js 同作者。类型定义完善。
  • mathjs:非常活跃,功能迭代快。体积较大,类型定义完善但配置复杂。

📊 总结对比表

特性big.jsbignumber.jsdecimal.jsmathjs
核心定位极简十进制计算通用高精度计算增强十进制计算全能数学引擎
API 风格类实例 (.plus)类实例 (.plus)类实例 (.add)函数式 (math.add)
三角函数❌ 不支持❌ 不支持✅ 支持✅ 支持
表达式解析❌ 不支持❌ 不支持❌ 不支持✅ 支持 (evaluate)
配置复杂度低中中高
典型场景货币计算通用高精度科学十进制工程/科学计算

💡 架构师建议

big.js 就像一把精致的小刀 🔪 —— 专注、锋利、无多余功能。如果你的需求仅仅是解决金额计算精度问题,且对包体积敏感,它是最佳选择。

bignumber.js 就像一把瑞士军刀 🇨🇭 —— 功能均衡,可靠性高。如果你不确定未来需求是否会扩展,或者需要处理加密货币等非传统货币单位,选它最稳妥。

decimal.js 就像一把专业手术刀 🏥 —— 在十进制领域比 bignumber.js 更严谨。如果你在进行严格的财务审计或科学记录,且需要三角函数支持,它比 bignumber.js 更合适。

mathjs 就像一个完整的工作台 🛠️ —— 功能强大但笨重。只有当你需要解析用户输入的数学公式、处理矩阵或进行复杂单位转换时,才值得引入它。

最终建议:对于 90% 的前端业务场景(电商、金融后台),bignumber.js 是综合性价比最高的选择。若追求极致轻量选 big.js,若涉及复杂数学运算选 mathjs。

如何选择: decimal.js vs bignumber.js vs big.js vs mathjs

  • decimal.js:

    选择 decimal.js 如果你需要更严格的十进制语义和更丰富的数学方法(如平方根、三角函数)。它与 bignumber.js 出自同一作者,但在处理纯十进制逻辑时往往表现更一致,适合对数值语义要求严格的系统。

  • bignumber.js:

    选择 bignumber.js 如果你需要一个社区认可度高、文档完善且功能平衡的库。它支持任意精度的十进制和非十进制运算,配置灵活,是大多数通用高精度计算场景(如加密货币、科学计算)的安全选择。

  • big.js:

    选择 big.js 如果你的项目对包体积非常敏感,且只需要基础的加减乘除运算。它设计极简,没有多余的配置,非常适合前端金融表单计算或货币处理,但在处理极大数值或非十进制运算时能力有限。

  • mathjs:

    选择 mathjs 如果你的需求不仅仅是高精度加减乘除,而是需要解析数学表达式、处理矩阵运算或进行复杂的科学计算。它的功能最强大,但体积也最大,适合构建计算器应用或工程类工具。

decimal.js的README

decimal.js

An arbitrary-precision Decimal type for JavaScript.

npm version npm downloads CDNJS


Features

  • Integers and floats
  • Simple but full-featured API
  • Replicates many of the methods of JavaScript's Number.prototype and Math objects
  • Also handles hexadecimal, binary and octal values
  • Faster, smaller, and perhaps easier to use than JavaScript versions of Java's BigDecimal
  • No dependencies
  • Wide platform compatibility: uses JavaScript 1.5 (ECMAScript 3) features only
  • Comprehensive documentation and test set
  • Used under the hood by math.js
  • Includes a TypeScript declaration file: decimal.d.ts

API

The library is similar to bignumber.js, but here precision is specified in terms of significant digits rather than decimal places, and all calculations are rounded to the precision (similar to Python's decimal module) rather than just those involving division.

This library also adds the trigonometric functions, among others, and supports non-integer powers, which makes it a significantly larger library than bignumber.js and the even smaller big.js.

For a lighter version of this library without the trigonometric functions see decimal.js-light.

Load

The library is the single JavaScript file decimal.js or ES module decimal.mjs.

Browser:

<script src='path/to/decimal.js'></script>

<script type="module">
  import Decimal from './path/to/decimal.mjs';
  ...
</script>

Node.js:

npm install decimal.js
const Decimal = require('decimal.js');

import Decimal from 'decimal.js';

import {Decimal} from 'decimal.js';

Use

In all examples below, semicolons and toString calls are not shown. If a commented-out value is in quotes it means toString has been called on the preceding expression.

The library exports a single constructor function, Decimal, which expects a single argument that is a number, string or Decimal instance.

x = new Decimal(123.4567)
y = new Decimal('123456.7e-3')
z = new Decimal(x)
x.equals(y) && y.equals(z) && x.equals(z)        // true

If using values with more than a few digits, it is recommended to pass strings rather than numbers to avoid a potential loss of precision.

// Precision loss from using numeric literals with more than 15 significant digits.
new Decimal(1.0000000000000001)         // '1'
new Decimal(88259496234518.57)          // '88259496234518.56'
new Decimal(99999999999999999999)       // '100000000000000000000'

// Precision loss from using numeric literals outside the range of Number values.
new Decimal(2e+308)                     // 'Infinity'
new Decimal(1e-324)                     // '0'

// Precision loss from the unexpected result of arithmetic with Number values.
new Decimal(0.7 + 0.1)                  // '0.7999999999999999'

As with JavaScript numbers, strings can contain underscores as separators to improve readability.

x = new Decimal('2_147_483_647')

String values in binary, hexadecimal or octal notation are also accepted if the appropriate prefix is included.

x = new Decimal('0xff.f')            // '255.9375'
y = new Decimal('0b10101100')        // '172'
z = x.plus(y)                        // '427.9375'

z.toBinary()                         // '0b110101011.1111'
z.toBinary(13)                       // '0b1.101010111111p+8'

// Using binary exponential notation to create a Decimal with the value of `Number.MAX_VALUE`.
x = new Decimal('0b1.1111111111111111111111111111111111111111111111111111p+1023')
// '1.7976931348623157081e+308'

Decimal instances are immutable in the sense that they are not changed by their methods.

0.3 - 0.1                     // 0.19999999999999998
x = new Decimal(0.3)
x.minus(0.1)                  // '0.2'
x                             // '0.3'

The methods that return a Decimal can be chained.

x.dividedBy(y).plus(z).times(9).floor()
x.times('1.23456780123456789e+9').plus(9876.5432321).dividedBy('4444562598.111772').ceil()

Many method names have a shorter alias.

x.squareRoot().dividedBy(y).toPower(3).equals(x.sqrt().div(y).pow(3))     // true
x.comparedTo(y.modulo(z).negated() === x.cmp(y.mod(z).neg())              // true

Most of the methods of JavaScript's Number.prototype and Math objects are replicated.

x = new Decimal(255.5)
x.toExponential(5)                       // '2.55500e+2'
x.toFixed(5)                             // '255.50000'
x.toPrecision(5)                         // '255.50'

Decimal.sqrt('6.98372465832e+9823')      // '8.3568682281821340204e+4911'
Decimal.pow(2, 0.0979843)                // '1.0702770511687781839'

// Using `toFixed()` to avoid exponential notation:
x = new Decimal('0.0000001')
x.toString()                             // '1e-7'
x.toFixed()                              // '0.0000001'

And there are isNaN and isFinite methods, as NaN and Infinity are valid Decimal values.

x = new Decimal(NaN)                                           // 'NaN'
y = new Decimal(Infinity)                                      // 'Infinity'
x.isNaN() && !y.isNaN() && !x.isFinite() && !y.isFinite()      // true

There is also a toFraction method with an optional maximum denominator argument.

z = new Decimal(355)
pi = z.dividedBy(113)        // '3.1415929204'
pi.toFraction()              // [ '7853982301', '2500000000' ]
pi.toFraction(1000)          // [ '355', '113' ]

All calculations are rounded according to the number of significant digits and rounding mode specified by the precision and rounding properties of the Decimal constructor.

For advanced usage, multiple Decimal constructors can be created, each with their own independent configuration which applies to all Decimal numbers created from it.

// Set the precision and rounding of the default Decimal constructor
Decimal.set({ precision: 5, rounding: 4 })

// Create another Decimal constructor, optionally passing in a configuration object
Dec = Decimal.clone({ precision: 9, rounding: 1 })

x = new Decimal(5)
y = new Dec(5)

x.div(3)                           // '1.6667'
y.div(3)                           // '1.66666666'

The value of a Decimal is stored in a floating point format in terms of its digits, exponent and sign, but these properties should be considered read-only.

x = new Decimal(-12345.67);
x.d                            // [ 12345, 6700000 ]    digits (base 10000000)
x.e                            // 4                     exponent (base 10)
x.s                            // -1                    sign

For further information see the API reference in the doc directory.

Test

To run the tests using Node.js from the root directory:

npm test

Each separate test module can also be executed individually, for example:

node test/modules/toFraction

To run the tests in a browser, open test/test.html.

Minify

Two minification examples:

Using uglify-js to minify the decimal.js file:

npm install uglify-js -g
uglifyjs decimal.js --source-map url=decimal.min.js.map -c -m -o decimal.min.js

Using terser to minify the ES module version, decimal.mjs:

npm install terser -g
terser decimal.mjs --source-map url=decimal.min.mjs.map -c -m --toplevel -o decimal.min.mjs
import Decimal from './decimal.min.mjs';

Licence

The MIT Licence