前端大数精度处理方案
问题
在我们常见的 JavaScript 数字运算中,小数和大数都是会让我们比较头疼的两个数据类型。
- 在大数运算中,由于 number 类型的数字长度限制,我们经常会遇到超出范围的情况。比如:后端给前端返回一个数字类型的 id,但是前端对这个 id 不做任何处理,直接使用到下一个给后端请求的时候,接口报错了,后端一查,说你前端 id 给传错了,然后前端一看果然是给传错了,但是自己又没有做什么,怎么拿的就怎么给后端了。原因就是因为 :
后端给的数字太大超过-9007199254740991 (-(2^53-1))
到9007199254740991(2^53-1)
之间的整数,前端 js 这块就会自动四舍五入,导致精度丢失。 - 而在小数点数字进行运算的过程中,JavaScript 又由于它的数据表示方式,从而导致了小数运算会有不准确的情况。最经典的一个例子就是 0.3 - 0.2,并不等于 0.1,而是等于 0.09999999999999998。
究其原因可以参考这篇文章:前端应该知道的JavaScript浮点数和大数的原理
解决方案
BigInt
因为 number 的基本类型不能超过2^53,不然就会精度丢失,为了解决这个限制,在 ECMAScript 标准中出现了BigInt,BigInt可以表示任意大的整数。
创建
- 直接BigInt去创建;
BigInt(value)
- 后面加个 n;
它在某些方面类似于 Number ,但是也有几个关键的不同点:不能用于 Math 对象中的方法;不能和任何 Number 实例混合运算,两者必须转换成同一种类型。在两种类型来回转换时要小心,因为 BigInt 变量在转换成 Number 变量时可能会丢失精度。
更多使用方式参考 MDN 官方文档:BigInt
第三方库
使用专门处理大数精度的第三方库,如 bignumber.js、big.js 或 decimal.js 等,是一种常见的解决方案。这些库提供了高精度的计算功能,可以避免 JavaScript 原生的浮点数精度问题。
库名称
|
简介
|
特征
|
包大小(压缩后)
|
适用场景
|
---|---|---|---|---|
Math.js |
适用于 JavaScript 和 Node.js 的扩展数学库。 它具有灵活的表达式解析器,支持符号计算,附带大量内置函数和常量,并提供集成解决方案来处理不同的数据类型,如数字、大数、复数、分数、单位和矩阵。 功能强大且易于使用。 |
|
197K | 科学计算、统计分析、数据可视化等领域 |
decimal.js | JavaScript 的任意精度 Decimal 类型 |
|
32 KB | 科学类应用 |
bignumber.js | 用于任意精度算术的 JavaScript 库 |
|
8 KB | 金融应用、货币计算等领域 |
big.js | 一个小型、快速、易于使用的库,用于任意精度的十进制算术 |
|
6 KB | 简单计算需求、小型项目等场景 |
自定义运算函数
自己编写处理大数的函数,以下以加法为例:
let a = "9876543210123456789000000000123";
let b = "1234567898765432100000012345678901";
function add(str1, str2) {
// 获取两个数字的最大长度
let maxLength = Math.max(str1.length, str2.length);
// 用0补齐长度,让它们两个长度相同
str1 = str1.padStart(maxLength, 0); // "0009876543210123456789000000000123"
str2 = str2.padStart(maxLength, 0); // "1234567898765432100000012345678901"
let temp = 0; // 每个位置相加之和
let flag = 0; // 进位:相加之和如果大于等于 10,则需要进位
let result = "";
for(let i=maxLength-1; i>=0; i--) {
// 获取当前位置的相加之和:字符串 1 + 字符串 2 + 进位数字
temp = parseInt(str1[i]) + parseInt(str2[i]) + flag;
// 获取下一个进位
flag = Math.floor(temp/10);
// 拼接结果字符串
result = temp%10 + result;
}
if(flag === 1) {
// 如果遍历完成后,flag 还剩 1,说明两数相加之后多了一位,类似于:95 + 10 = 105
result = "1" + result;
}
return result;
}
实现相对比较麻烦,且容易出错,不推荐。
case 实践
以后端返回数字类型的 id 超出 2^53-1 这个 case 为例,采用第三方库 big.js 来处理,封装处理函数如下:
// 引入 big.js库
const Big = require('big.js');
function processId(id) {
let processedId;
if (typeof id === 'string') {
// 将字符串类型的 id 转换为 Big 对象
processedId = new Big(id);
} else if (typeof id === 'number') {
// 将数字类型的 id 转换为 Big 对象
processedId = new Big(id.toString());
} else {
throw new Error('Invalid id type. Expected string or number.');
}
// 判断 id 是否超出 JavaScript Number 类型范围
if (!Big(processedId).eq(id)) {
// 使用 big.js 库处理超出范围的 id
processedId = Big(id);
}
return processedId.toString();
}
上述代码中,我们首先引入了 big.js 库,并定义了一个 processId 函数。该函数接受一个id作为参数,可以是字符串类型或者数字类型。
在函数中,我们首先判断 id 的类型,如果是字符串类型,将其转换为 Big 对象;如果是数字类型,则先转换成字符串再转换为 Big 对象。
然后,我们判断 id 是否超出了JavaScript Number类型的范围,通过使用 Big 对象对比原始 id 和转换后的 processedId 是否相等来判断。如果不相等,表示 id 超出了JavaScript Number 类型的范围,我们使用 Big 对象重新处理该 id。
最后,我们将处理后的id通过 toString() 方法转换为字符串,并返回该处理后的 id。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】