在 JavaScript 中,所有数字都采用 IEEE 754 双精度浮点格式表示。由于这种表示法的限制,JavaScript 中的 Number 类型有其最大值(Number.MAX_VALUE,约 1.7976931348623157e+308),以及安全整数的最大值(Number.MAX_SAFE_INTEGER,2^53 - 1)。当数值超出这些范围时,可能会出现以下问题:

  • 超出 Number.MAX_VALUE:超出此值的数值会变为 Infinity

  • 超过安全整数范围:大于 Number.MAX_SAFE_INTEGER 的整数可能会失去精度。

为了正确处理这些情况,我们可以采用以下几种方案:

使用 BigInt

ES2020 引入了 BigInt,可以用来表示任意大小的整数。BigInt 使用 n 后缀表示,例如:

1
2
3
// 创建一个超过 Number.MAX_SAFE_INTEGER 的整数
const bigIntValue = 9007199254740993n;
console.log(bigIntValue); // 输出:9007199254740993n

BigInt 特点

  • 任意精度整数:能够表示非常大的整数,不会因为超出 Number 范围而变成 Infinity。

  • 运算符支持:BigInt 支持常见的算术运算,但注意不能与普通 Number 混用(需显式转换)。

例如:

1
2
3
const a = 9007199254740993n;
const b = 2n;
const sum = a + b; // 9007199254740995n

注意事项

  • 不支持小数:BigInt 只能表示整数,无法表示浮点数。如果需要处理大数的浮点运算,可以考虑使用第三方库。

使用第三方库

如果你的应用需要处理超大数值或高精度浮点运算,可以考虑使用诸如 bignumber.jsdecimal.js 这样的库。

示例:使用 bignumber.js

  1. 安装 bignumber.js:
1
npm install bignumber.js --save
  1. 使用示例:
1
2
3
4
5
6
7
8
9
const BigNumber = require('bignumber.js');

// 创建一个大数
const bigNum = new BigNumber('1.7976931348623157e+308');
console.log(bigNum.toString());

// 进行运算
const result = bigNum.plus('1e+292');
console.log(result.toString());

这些库不仅能处理非常大的整数,还能处理高精度的小数运算,适用于金融计算、科学计算等场景。

处理 Infinity 和 NaN

当一个计算结果超出 Number.MAX_VALUE 时,JavaScript 会返回 Infinity。在实际开发中,可以通过检测这种情况来采取特定的处理策略:

1
2
3
4
5
const num = Number.MAX_VALUE * 2;
if (num === Infinity) {
console.warn('数值超出 Number.MAX_VALUE,变为 Infinity');
// 可以选择使用 BigInt 或第三方库进行处理
}

大数计算三方库实现原理

数字的表示与存储

大数库通常采用类似科学计数法的表示方式,将一个数表示为:

1
number = sign × coefficient × 10^exponent
  • sign:正负符号(通常用 1 或 -1 表示)。

  • coefficient(又称为 mantissa):一个表示数字有效位的整数或数字字符串。

  • exponent:整数,表示系数需要乘以 10 的幂次。

例如,数字 12345.6789 可能会被转换成:

  • sign: 1

  • coefficient: 123456789

  • exponent: -4

这种表示方式可以精确描述任意大小的数,同时允许库对系数进行精细的控制,从而实现任意精度运算。

很多大数库内部会将 coefficient 存储为一个字符串或者一个数组(每个元素代表若干位数字),以便于进行逐位运算。通过字符串或数组,可以突破 JavaScript 内部整数最大安全值的限制,实现大数的存储与计算。

算法实现

加法和减法

对于加法和减法,大数库通常遵循以下步骤:

  1. 对齐指数:如果两个数的 exponent 不同,需要将它们转换到相同的指数。例如,调整系数,使得指数相同,从而使得数字能够直接相加或相减。

  2. 逐位运算:将对齐后的系数进行逐位加法或减法,处理进位和借位问题。

  3. 标准化结果:最后,调整结果的系数和指数,确保系数在规范范围内,并应用必要的舍入规则。

乘法

乘法的实现通常涉及到对两个系数进行多位乘法:

  1. 乘以整数:把两个系数视作整数进行乘法运算,可以采用传统的乘法算法或更高效的算法(如 Karatsuba 算法)。

  2. 指数相加:两个数相乘后,指数部分相加。

  3. 处理进位和标准化:乘积可能会产生额外的位数,最后需要标准化结果。

除法

除法的计算相对复杂,大数库可能采用类似长除法的方法来计算系数的商,并调整指数。许多库还支持设置精度和舍入模式,以保证运算结果满足用户需求。

舍入和精度控制

大数库通常允许用户设置运算精度和不同的舍入模式(如向上舍入、向下舍入、四舍五入等),在每次运算后对结果进行格式化,确保输出符合预期精度。

代码示例:简单大数加法

下面提供一个非常简化的示例,展示如何用纯 JavaScript 模拟大数加法。注意,这只是一个示例,实际库(如 bignumber.js 或 decimal.js)实现要复杂得多,涉及错误处理、各种运算和优化策略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 简单大数表示:采用对象 { sign, coefficient, exponent }
function BigNumber(sign, coefficient, exponent) {
this.sign = sign; // 1 或 -1
this.coefficient = coefficient; // 字符串形式的整数
this.exponent = exponent; // 整数
}

// 简单加法:假设两个数的 exponent 相同
BigNumber.prototype.add = function (other) {
if (this.exponent !== other.exponent) {
throw new Error('示例仅支持相同指数的加法');
}
// 将系数转换为大整数形式(字符串相加)
// 这里简单采用内置 BigInt 来演示运算,实际库通常自己实现大整数算法
const a = BigInt(this.coefficient);
const b = BigInt(other.coefficient);
const sum = a + b;
return new BigNumber(this.sign, sum.toString(), this.exponent);
};

// 示例使用
const num1 = new BigNumber(1, '123456789123456789', -4);
const num2 = new BigNumber(1, '987654321987654321', -4);
const result = num1.add(num2);
console.log(`Result: ${result.sign * Number(result.coefficient)}e${result.exponent}`);
// 实际输出:Result: 1.1111111111111111e-4(示例仅用于说明原理)

在上述示例中,我们将大数表示为一个对象,包含符号、系数(以字符串存储)和指数。加法操作通过内置 BigInt 模拟,但实际库往往采用自定义的算法来避免使用 BigInt,从而兼容更多环境和满足更高的性能需求。


总结

  • 使用 BigInt:对于需要处理超大整数的场景,BigInt 是一个原生解决方案,但仅适用于整数。

  • 使用第三方库:如 bignumber.js、decimal.js 能够处理大数和高精度浮点运算,适合金融、科学计算等领域。

  • 处理 Infinity 和 NaN:在计算过程中检测特殊值,及时切换到其他处理方案,保证应用稳定性。

希望可以帮到你!

Happy Coding!