问题
在我们常见的 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。
标签:大数,Big,前端,JavaScript,js,let,类型,id,精度 From: https://www.cnblogs.com/zhilin/p/17661829.html