JS超过Number最大值的数怎么处理?
前端最容易踩的大数坑通常有两类:
- 金额、计数、ID 这类“看起来是整数”的东西,一旦超过
2^53 - 1(Number.MAX_SAFE_INTEGER),就会开始丢精度; - 真的特别大的浮点数,超过
Number.MAX_VALUE直接变成Infinity。
原因也很简单:JavaScript 的 Number 用的是 IEEE 754 双精度浮点。它能表示很大的范围,但整数精度只有 53 位。
超出 Number.MAX_VALUE:超出此值的数值会变为
Infinity。超过安全整数范围:大于
Number.MAX_SAFE_INTEGER的整数可能会失去精度。
遇到这些问题时,别急着“找一个库来解决”。先想清楚:你要处理的是“大整数”、还是“高精度小数”(比如金融)?两者方案不一样。
使用 BigInt
ES2020 引入了 BigInt,用来表示任意大小的整数。它的心智模型也很直接:只要是整数,就不会丢精度。
1 | // 创建一个超过 Number.MAX_SAFE_INTEGER 的整数 |
BigInt 特点
任意精度整数:能够表示非常大的整数,不会因为超出 Number 范围而变成 Infinity。
运算符支持:BigInt 支持常见的算术运算,但不能和
Number直接混算(需要显式转换)。
例如:
1 | const a = 9007199254740993n; |
注意事项
- 不支持小数:BigInt 只能表示整数。涉及金额/利率这类小数精度需求,通常要用 decimal 类库或“分/厘”为单位的整数。
- JSON 不友好:
JSON.stringify不能直接序列化 BigInt(会报错),接口传输更常见的做法仍然是用字符串。
使用第三方库
如果你需要的是“高精度小数”(金融计算、计费、科学计算),那 BigInt 往往不够用。这时更合适的是 bignumber.js 或 decimal.js 这类库,它们会自己维护精度和舍入规则。
示例:使用 bignumber.js
- 安装 bignumber.js:
1 | npm install bignumber.js --save |
- 使用示例:
1 | const BigNumber = require('bignumber.js'); |
这些库不仅能处理非常大的整数,还能处理高精度的小数运算,适用于金融计算、科学计算等场景。
处理 Infinity 和 NaN
当计算结果超出 Number.MAX_VALUE,JS 会给你一个 Infinity。业务上怎么处理取决于场景:提示用户、做截断、还是切换到更高精度的计算方式。
1 | const num = Number.MAX_VALUE * 2; |
大数计算三方库实现原理
数字的表示与存储
大数库一般会用类似科学计数法的结构来存一个数:
1 | number = sign × coefficient × 10^exponent |
sign:正负符号(通常用 1 或 -1 表示)。
coefficient(又称为 mantissa):一个表示数字有效位的整数或数字字符串。
exponent:整数,表示系数需要乘以 10 的幂次。
例如,数字 12345.6789 可能会被转换成:
sign: 1
coefficient: 123456789
exponent: -4
这样做的好处是:数字可以无限长;精度/舍入规则也可以由库自己控制。
很多大数库内部会将 coefficient 存储为一个字符串或者一个数组(每个元素代表若干位数字),以便于进行逐位运算。通过字符串或数组,可以突破 JavaScript 内部整数最大安全值的限制,实现大数的存储与计算。
算法实现
加法和减法
对于加法和减法,大数库通常遵循以下步骤:
对齐指数:如果两个数的 exponent 不同,需要将它们转换到相同的指数。例如,调整系数,使得指数相同,从而使得数字能够直接相加或相减。
逐位运算:将对齐后的系数进行逐位加法或减法,处理进位和借位问题。
标准化结果:最后,调整结果的系数和指数,确保系数在规范范围内,并应用必要的舍入规则。
乘法
乘法的实现通常涉及到对两个系数进行多位乘法:
乘以整数:把两个系数视作整数进行乘法运算,可以采用传统的乘法算法或更高效的算法(如 Karatsuba 算法)。
指数相加:两个数相乘后,指数部分相加。
处理进位和标准化:乘积可能会产生额外的位数,最后需要标准化结果。
除法
除法的计算相对复杂,大数库可能采用类似长除法的方法来计算系数的商,并调整指数。许多库还支持设置精度和舍入模式,以保证运算结果满足用户需求。
舍入和精度控制
大数库通常允许用户设置运算精度和不同的舍入模式(如向上舍入、向下舍入、四舍五入等),在每次运算后对结果进行格式化,确保输出符合预期精度。
代码示例:简单大数加法
下面是一个非常简化的示例,用来帮助理解“系数 + 指数”的思路。真实的大数库要复杂得多:要处理精度、舍入、符号、异常、性能优化等等。
1 | // 简单大数表示:采用对象 { sign, coefficient, exponent } |
在上述示例中,我们将大数表示为一个对象,包含符号、系数(以字符串存储)和指数。加法操作通过内置 BigInt 模拟,但实际库往往采用自定义的算法来避免使用 BigInt,从而兼容更多环境和满足更高的性能需求。
总结
- 只是“大整数”:能用 BigInt 就用 BigInt;接口传输优先用字符串,别强行用 Number 扛。
- 需要“高精度小数”:上 decimal/bignumber 类库,明确精度与舍入规则。
- 碰到 Infinity/NaN:先做检测与兜底,再考虑是不是要切换计算策略。
希望可以帮到你!
Happy Coding!
