为什么 num === num - 1 结果会为true?

242 阅读4分钟

JavaScript 中,num === num - 1 的等式成立需要满足数值或类型的特殊条件。

典型场景

1. 无穷大(Infinity)

numInfinity 时,任何数学运算都不会改变其值,因此:

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 双精度浮点数的精度限制,连续的整数可能无法区分,此时 numnum-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,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『前端也能这么有趣』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。