在 JavaScript 中,num === num - 1
的等式成立需要满足数值或类型的特殊条件。
典型场景
1. 无穷大(Infinity)
当 num
为 Infinity
时,任何数学运算都不会改变其值,因此:
const num = Infinity;
console.log(num === num - 1); // true
console.log(num - 1); //Infinity
因为 Infinity - 1
仍为 Infinity
,类型和值均相同。
2. 超出精度范围的大数
当 num
超过 JavaScript 的整数精确表示范围(超过 Number.MAX_SAFE_INTEGER
,即 2^53 - 1
)时,由于 IEEE 754 双精度浮点数的精度限制,连续的整数可能无法区分,此时 num
和 num-1
在内存中有可能被存储为相同的浮点数。
const num = Number.MAX_SAFE_INTEGER + 3;
console.log(num === num - 1); //false
console.log(num,num-1);
//9007199254740994 9007199254740992
const num1 = Number.MAX_SAFE_INTEGER + 4;
console.log(num1 === num1 - 1); //true
console.log(num1,num1-1);
//9007199254740996 9007199254740996
我们看一下 [MAX_SAFE_INTEGER,MAX_SAFE_INTEGER+6] 的值都是什么:
console.log(Number.MAX_SAFE_INTEGER);
console.log(Number.MAX_SAFE_INTEGER+1);
console.log(Number.MAX_SAFE_INTEGER+2);
console.log(Number.MAX_SAFE_INTEGER+3);
console.log(Number.MAX_SAFE_INTEGER+4);
console.log(Number.MAX_SAFE_INTEGER+5);
console.log(Number.MAX_SAFE_INTEGER+6);
从上面结果中我们可以看到 [MAX_SAFE_INTEGER,MAX_SAFE_INTEGER+6] 的值是有点混乱的,所以超过 Number.MAX_SAFE_INTEGER
的数字我们就不能用常规的计算方法了。
怎么规避
验证安全整数范围
通过 Number.isSafeInteger() 可以检测数值是否在安全范围内:
console.log(Number.isSafeInteger(666)); //true
console.log(Number.isSafeInteger(Number.MAX_SAFE_INTEGER)); //true
console.log(Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 6)); //false
console.log(Number.isSafeInteger(9007199254740999)); //false
使用 BigInt 处理大整数
JavaScript 从 ES2020 开始支持 BigInt 类型,可直接处理任意精度的整数。对于可能超出安全整数范围的整数,如果需要精确计算,我们可以将数值转换为 BigInt 类型。
// 创建 BigInt(后缀加 n 或调用构造函数)
const a = 123456789012345678901234567890n;
const b = BigInt("987654321098765432109876543210");
// 基本运算
console.log(a + b); // 1111111110111111111011111111100n
console.log(a * b); // 12193263113702179522374638011189951891063930922378900n
// 注意事项:BigInt 不能与普通 Number 混合运算
const c = 10;
console.log(a + c); // 报错,需转换为同类型:a + BigInt(c)
还有要注意的是:BigInt 只能进行纯整数运算
那么如果涉及到高精度浮点数计算我们该怎么办呢?
bignumber.js
bignumber.js 是一个比较成熟的高精度计算库,涉及浮点数或更复杂的运算时我们可以使用这个库。
import BigNumber from 'bignumber.js';
const x = new BigNumber("123456789.123456789");
const y = new BigNumber("987654321.987654321");
// 高精度运算(支持小数)
console.log(x.plus(y).toString()); // "1111111111.11111111"
console.log(x.multipliedBy(y).toFixed(2)); // "121932631137021795.22"
当然,如果你喜欢,我们也可以自己简单实现一个大数运算,这里我们一起来实现一个大数加法试试
手写大数加法
思路
简单来说就是要模拟一个加法竖式的运算过程,所以我们需要思考一下自己在做加法竖式计算的时候是怎么做的?
- 1、个位数对齐,从后往前依次相加
不管是否带有小数,我们都是将两个数字的个位数对齐来做计算。
- 2、检查是否需要进位和是否有进位
相加大于等于10的时候,需要进位加1
- 3、两个字符串都遍历完时即可得出结果
代码实现
function addStrings(num1, num2) {
num1 = (num1 || "0") + "";
num2 = (num2 || "0") + "";
let dec1Len = num1.split(".")[1]?.length || 0,
dec2Len = num2.split(".")[1]?.length || 0;
if (dec1Len || dec2Len) {
if (!num1.includes(".")) {
num1 += ".";
}
if (!num2.includes(".")) {
num2 += ".";
}
const maxDecLen = Math.max(dec1Len, dec2Len);
num1 += "0".repeat(maxDecLen - dec1Len);
num2 += "0".repeat(maxDecLen - dec2Len);
}
let i = num1.length - 1,
j = num2.length - 1,
add = 0,
ans = "";
//从后往前遍历
while (i >= 0 || j >= 0 || add != 0) {
if (num1[i] === ".") {
ans = "." + ans;
i--;
j--;
continue;
}
//每一位转换为数字
const x = i >= 0 ? num1.charAt(i) - "0" : 0;
const y = j >= 0 ? num2.charAt(j) - "0" : 0;
//保存该位的计算结果
const result = x + y + add;
ans = (result % 10) + ans;
add = Math.floor(result / 10);
i -= 1;
j -= 1;
}
return ans;
}
测试一下
console.log(
addStrings(
"11111111111111111111111111111.11111111111111111111",
"11111111111111111111111111111.99999999999999999999"
)
);
//22222222222222222222222222223.11111111111111111110
console.log(
addStrings(
"11111111111111111111111111111.11111111111111111111",
"11111111111111111111111111111"
)
);
//22222222222222222222222222222.11111111111111111111
console.log(addStrings("9", "2"));
//11
公众号
关注公众号『 前端也能这么有趣 』,获取更多有趣内容。
发送 加群 还可以加入群聊,一起来学习(摸鱼)吧~
说在后面
🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『
前端也能这么有趣
』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。