安全整数和 Number.isSafeInteger()

JavaScript 能够准确表示的整数范围在-2^532^53之间(不含两个端点),超过这个范围,无法精确表示这个值。

  1. Math.pow(2, 53) // 9007199254740992
  2. 9007199254740992 // 9007199254740992
  3. 9007199254740993 // 9007199254740992
  4. Math.pow(2, 53) === Math.pow(2, 53) + 1
  5. // true

上面代码中,超出 2 的 53 次方之后,一个数就不精确了。

ES6 引入了Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限。

  1. Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1
  2. // true
  3. Number.MAX_SAFE_INTEGER === 9007199254740991
  4. // true
  5. Number.MIN_SAFE_INTEGER === -Number.MAX_SAFE_INTEGER
  6. // true
  7. Number.MIN_SAFE_INTEGER === -9007199254740991
  8. // true

上面代码中,可以看到 JavaScript 能够精确表示的极限。

Number.isSafeInteger()则是用来判断一个整数是否落在这个范围之内。

  1. Number.isSafeInteger('a') // false
  2. Number.isSafeInteger(null) // false
  3. Number.isSafeInteger(NaN) // false
  4. Number.isSafeInteger(Infinity) // false
  5. Number.isSafeInteger(-Infinity) // false
  6. Number.isSafeInteger(3) // true
  7. Number.isSafeInteger(1.2) // false
  8. Number.isSafeInteger(9007199254740990) // true
  9. Number.isSafeInteger(9007199254740992) // false
  10. Number.isSafeInteger(Number.MIN_SAFE_INTEGER - 1) // false
  11. Number.isSafeInteger(Number.MIN_SAFE_INTEGER) // true
  12. Number.isSafeInteger(Number.MAX_SAFE_INTEGER) // true
  13. Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1) // false

这个函数的实现很简单,就是跟安全整数的两个边界值比较一下。

  1. Number.isSafeInteger = function (n) {
  2. return (typeof n === 'number' &&
  3. Math.round(n) === n &&
  4. Number.MIN_SAFE_INTEGER <= n &&
  5. n <= Number.MAX_SAFE_INTEGER);
  6. }

实际使用这个函数时,需要注意。验证运算结果是否落在安全整数的范围内,不要只验证运算结果,而要同时验证参与运算的每个值。

  1. Number.isSafeInteger(9007199254740993)
  2. // false
  3. Number.isSafeInteger(990)
  4. // true
  5. Number.isSafeInteger(9007199254740993 - 990)
  6. // true
  7. 9007199254740993 - 990
  8. // 返回结果 9007199254740002
  9. // 正确答案应该是 9007199254740003

上面代码中,9007199254740993不是一个安全整数,但是Number.isSafeInteger会返回结果,显示计算结果是安全的。这是因为,这个数超出了精度范围,导致在计算机内部,以9007199254740992的形式储存。

  1. 9007199254740993 === 9007199254740992
  2. // true

所以,如果只验证运算结果是否为安全整数,很可能得到错误结果。下面的函数可以同时验证两个运算数和运算结果。

  1. function trusty (left, right, result) {
  2. if (
  3. Number.isSafeInteger(left) &&
  4. Number.isSafeInteger(right) &&
  5. Number.isSafeInteger(result)
  6. ) {
  7. return result;
  8. }
  9. throw new RangeError('Operation cannot be trusted!');
  10. }
  11. trusty(9007199254740993, 990, 9007199254740993 - 990)
  12. // RangeError: Operation cannot be trusted!
  13. trusty(1, 2, 3)
  14. // 3