学习链接

现代 JavaScript 教程:数字类型

阮一峰:数值

阮一峰:数值的扩展

数据类型-Number

函数 toFixed(n) 将数字舍入到小数点后 n 位,并以字符串形式返回结果。

浮点数计算

在内部,数字全是以 64 位格式浮点数形式存储的,需要整数运算时,转为 32 位整数再计算。

1 位用于符号位,11 位用于存储指数部分,52 位用于存储小数部分(即有效数字)。

15 位的十进制数都可精确处理,超过 数据类型-Number - 图1 时不保证精度。

浮点数的精度损失

使用二进制数字系统无法 精确 存储 0.1 或 0.2,就像没有办法将三分之一存储为十进制小数一样。

因为 1/3 无法用 10 的整数次幂来进行精确表示,0.10.2 也无法用 2 的整数次幂来进行精确表示。

IEEE-754 数字格式通过将数字舍入到最接近的可能数字来解决此问题。

这些舍入规则容易让人们忽略掉“极小的精度损失”,但是它确实存在

  1. 0.1 + 0.2 !== 0.3 // true
  2. 0.1 + 0.2 // 0.30000000000000004
  1. 0.1.toFixed(20) // '0.10000000000000000555'
  2. 0.2.toFixed(20) // '0.20000000000000001110'
  3. (0.1 + 0.2).toFixed(20) // '0.30000000000000004441'
  4. 0.3.toFixed(20) // '0.29999999999999998890'

当我们对两个数字进行求和时,它们的“精度损失”会叠加起来

这就是为什么 0.1 + 0.2 不等于 0.3

解决方案

最可靠的方法是借助方法 toFixed(n) 对结果进行舍入

  1. let sum = 0.1 + 0.2;
  2. sum.toFixed(2) // '0.30'

请注意,toFixed 总是返回一个字符串

我们可以将数字临时乘以 100(或更大的数字),将其转换为整数,进行数学运算,然后再除回。当我们使用整数进行数学运算时,误差会有所减少,但仍然可以在除法中得到:

  1. (0.1 * 10 + 0.2 * 10) / 10 // 0.3
  2. (0.28 * 100 + 0.14 * 100) / 100 // 0.4200000000000001

因此,乘/除法可以减少误差,但不能完全消除误差

补充

  1. 9999999999999999 // 显示 10000000000000000

出现了同样的问题:精度损失。有 64 位来表示该数字,其中 52 位可用于表示有效数字,但这还不够。所以最不重要的数字就消失了。

JavaScript 不会在此类事件中触发 error。它会尽最大努力使数字符合所需的格式,但不幸的是,这种格式不够大到满足需求。

常规数字检测

  • isNaN(value)
    先将其参数转换为数字(调用 Number()),然后检测它是否为 NaN

  • isFinite(value)
    先将其参数转换为数字(调用 Number()),如果它是常规数字,而不是 NaN/Infinity/-Infinity,则返回 true

  • Number.isNaN(value)
    检查一个值是否为 NaN只对数值有效,不进行转换

  • Number.isFinite(value)
    检查一个值是否为常规数字,只对数值有效,不进行转换

  1. isNaN(NaN) // true
  2. isNaN("NaN") // true
  3. Number.isNaN(NaN) // true
  4. Number.isNaN("NaN") // false
  5. Number.isNaN(1) // false
  6. isFinite(25) // true
  7. isFinite("25") // true
  8. Number.isFinite(25) // true
  9. Number.isFinite("25") // false

字符串转数字

“硬” 转换

使用加号 +Number() 的数字转换是严格的。如果一个值不完全是一个数字,就会失败

  1. +"100px" // NaN

唯一的例外是字符串开头或结尾的空格,因为它们会被忽略。

“软” 转换

使用 parseInt/parseFloat 进行“软”转换,它从字符串中读取数字,直到无法读取为止,然后返回在发生 error 前可以读取到的值。

(与 Number.parseInt/Number.parseFloat 完全一致)

函数 parseInt 返回一个整数,而 parseFloat 返回一个浮点数

  1. parseInt(' 100px ')// 100
  2. parseFloat(' 12.5em') // 12.5
  3. parseInt(' 12.3 ') // 12,只有整数部分被返回了
  4. parseFloat(' 12.3.4 ') // 12.3,在第二个点出停止了读取
  5. parseInt('a123') // NaN,第一个符号停止了读取

parseInt(str, radix)

parseInt() 函数具有可选的第二个参数。它指定了数字系统的基数,因此 parseInt 还可以解析十六进制数字、二进制数字等的字符串:

  1. alert( parseInt('0xff', 16) ); // 255
  2. alert( parseInt('ff', 16) ); // 255,没有 0x 仍然有效
  3. alert( parseInt('2n9c', 36) ); // 123456

特殊数值

+0-0

  1. -0 === +0 // true
  2. 0 === -0 // true
  3. 0 === +0 // true
  4. +0 // 0
  5. -0 // 0
  6. (-0).toString() // '0'
  7. (+0).toString() // '0'
  8. (1 / +0) === (1 / -0) // false
  9. (1 / +0) // Infinity
  10. (1 / -0) // -Infinity

NaN

Not a Number

  1. typeof NaN // 'number'
  2. NaN === NaN // false
  3. 5 - 'x' // NaN
  4. Math.sqrt(-1) // NaN
  5. 0 / 0 // NaN

Infinity-Infinity

  1. 1/0 // Infinity
  2. -1/0 // -Infinity
  3. // 数学计算中当作无穷
  4. 0 * Infinity // NaN
  5. 0 / Infinity // 0
  6. Infinity / 0 // Infinity
  7. Infinity + Infinity // Infinity
  8. Infinity * Infinity // Infinity
  9. Infinity - Infinity // NaN
  10. Infinity / Infinity // NaN
  11. // 与 undefined 计算都是 NaN

Object.is()

有一个特殊的内建方法 Object.is,它类似于 === 一样对值进行比较,但它对于两种边缘情况更可靠:

  1. NaNObject.is(NaN, NaN) === true
  2. 0-0Object.is(0, -0) === false

在所有其他情况下,Object.is(a, b)a === b 相同。

这种比较方式经常被用在 JavaScript 规范中。当内部算法需要比较两个值是否完全相同时,它使用 Object.is(内部称为 SameValue)。