◆

JavaScript Number 数值类型详解与精度问题

2023-02-02 01:370

整数和浮点数

JavaScript 内部,所有数字都是以 64 位浮点数形式储存,即使整数也是如此。所以,1 与 1.0 是相同的,是同一个数。 这就是说,JavaScript 语言的底层根本没有整数,所有数字都是小数(64 位浮点数)。

如果某些运算必须是整数才能完成,此时 JavaScript 会自动把 64 位浮点数,转成 32 位整数,然后再进行运算

由于浮点数不是精确的值,涉及小数运算时得到的结果可能不是预期的

console.log(0.1 + 0.2 === 0.3);
// false

console.log(0.3 / 0.1);
// 2.9999999999999996

console.log(0.3 - 0.2 === 0.2 - 0.1);
// false

数值精度

JavaScript 浮点数的 64 个二进制位,从最左边开始,是这样组成的。

  • 第 1 位:符号位,0 表示正数,1 表示负数
  • 第 2 位到第 12 位(共 11 位):指数部分
  • 第 13 位到第 64 位(共 52 位):小数部分(即有效数字)

符号位决定了一个数的正负,指数部分决定了数值的大小,小数部分决定了数值的精度。

精度最多只能到 53 个二进制位,这意味着,绝对值小于 2 的 53 次方的整数,即 -2^53 到 2^53,都可以精确表示。

下面代码中,大于 2 的 53 次方以后,整数运算的结果开始出现错误。所以,大于 2 的 53 次方的数值,都无法保持精度

Math.pow(2, 53);
// 9007199254740992

Math.pow(2, 53) + 1;
// 9007199254740992

Math.pow(2, 53) + 2;
// 9007199254740994

Math.pow(2, 53) + 3;
// 9007199254740996

Math.pow(2, 53) + 4;
// 9007199254740996

数值范围

根据标准,64 位浮点数的指数部分的长度是 11 个二进制位,意味着指数部分的最大值是 2047(2 的 11 次方减 1)。 也就是说,64 位浮点数的指数部分的值最大为 2047,分出一半表示负数,则 JavaScript 能够表示的数值范围为 2^1024 到 2^1023(开区间),超出这个范围的数无法表示。

如果一个数大于等于 2 的 1024 次方,那么就会发生“正向溢出”,即 JavaScript 无法表示这么大的数,这时就会返回 Infinity。

Math.pow(2, 1024); // Infinity

如果一个数小于等于 2 的 -1075 次方(指数部分最小值 -1023,再加上小数部分的 52 位),那么就会发生为“负向溢出”, 即 JavaScript 无法表示这么小的数,这时会直接返回 0。

Math.pow(2, -1075); // 0

示例

var x = 0.5;

for (var i = 0; i < 25; i++) {
  x = x * x;
}

x; // 0

数值的表示法

科学计数法表示: 允许字母 e 或 E 的后面,跟着一个整数,表示这个数值的指数部分

123e3; // 123000
123e-3 - // 0.123
  3.1e2 - // -310
  3.29e3; // -3290

某些情况下 JavaScript 会自动将数值转为科学计数法表示,其他情况都采用字面形式直接表示。

小数点前的数字多于 21 位。

1234567890123456789012;
// 1.2345678901234568e+21

123456789012345678901;
// 123456789012345680000

小数点后的零多于 5 个。

// 小数点后紧跟5个以上的零,
// 就自动转为科学计数法
0.0000003; // 3e-7

// 否则,就保持原来的字面形式
0.000003; // 0.000003

数值的进制

  • 十进制:没有前导 0 的数值。
  • 八进制:有前缀 0o 或 0O 的数值,或者有前导 0、且只用到 0-7 的八个阿拉伯数字的数值。
  • 十六进制:有前缀 0x 或 0X 的数值。
  • 二进制:有前缀 0b 或 0B 的数值。

默认情况下,JavaScript 内部会自动将八进制、十六进制、二进制转为十进制。下面是一些例子。

0xff; // 255
0o377; // 255
0b11; // 3

如果八进制、十六进制、二进制的数值里面,出现不属于该进制的数字,就会报错。

// 0xzz // 报错
// 0o88 // 报错
// 0b22 // 报错

**通常来说,有前导 0 的数值会被视为八进制,但是如果前导 0 后面有数字 8 和 9,则该数值被视为十进制 **

0;
888; // 888
0777; // 511

+0 与 -0

区别就是 64 位浮点数表示法的符号位不同。它们是等价的。

-0 === +0; // true
0 === -0; // true
0 === +0; // true

console.log(JSON.stringify(-0)); // 0
console.log(JSON.parse(-0)); // 0

唯一有区别的场合是,+0 或 -0 当作分母,返回的值是不相等的。

1 / +0 === 1 / -0; // false

上面的代码之所以出现这样结果,是因为除以正零得到 +Infinity,除以负零得到 -Infinity,这两者是不相等的

Number 对象

Number 对象是数值对应的包装对象,可以作为构造函数使用,也可以作为工具函数使用。

在浏览器打印出来的原型属性

Number数据类型详解

作为构造函数时,它用于生成值为数值的对象。

var n = new Number(1);
typeof n; // "object"

作为工具函数时,它可以将任何类型的值转为数值。

Number(true); // 1
  • 如果参数无法被转换为数字,则返回 NaN。
  • 在非构造器上下文中 (如:没有 new 操作符),Number 能被用来执行类型转换。
  • 整数类型的范围 -2^53 到 2^53 之间(不含两个端点),超过这个范围,无法精确表示这个整数。

JavaScript 的 Number 对象是经过封装的能让你处理数字值的对象。Number 对象由 Number() 构造器创建。

new Number(value);
var a = new Number('123'); // a === 123 is false
var b = Number('123'); // b === 123 is true
a instanceof Number; // is true
b instanceof Number; // is false

Number 的静态属性

Number 对象拥有以下一些静态属性(即直接定义在 Number 对象上的属性,而不是定义在实例上的属性)。

  • Number.MAX_VALUE 属性表示在 JavaScript 里所能表示的最大数值。值接近于 1.79E+308。大于 MAX_VALUE 的值代表 "Infinity"。
  • Number.MIN_VALUE 属性表示在 JavaScript 中所能表示的最小的正值。是 JavaScript 里最接近 0 的正值,而不是最小的负值。约为 5e-324。小于 MIN_VALUE 的值将会转换为 0。
  • Number.NEGATIVE_INFINITY 属性表示负无穷大。Number.NEGATIVE_INFINITY 的值和全局对象的 Infinity 属性的负值相同
    • 该值的行为同数学上的无穷大(infinity)有一点儿不同:
    • 1、任何正值,包括 POSITIVE_INFINITY,乘以 NEGATIVE_INFINITY 为 NEGATIVE_INFINITY。
    • 2、任何负值,包括 NEGATIVE_INFINITY,乘以 NEGATIVE_INFINITY 为 POSITIVE_INFINITY。
    • 3、0 乘以 NEGATIVE_INFINITY 为 NaN.
    • 4、NaN 乘以 NEGATIVE_INFINITY 为 NaN.
    • 5、NEGATIVE_INFINITY 除以任何负值(除了 NEGATIVE_INFINITY)为 POSITIVE_INFINITY。
    • 6、NEGATIVE_INFINITY 除以任何正值(除了 POSITIVE_INFINITY)为 NEGATIVE_INFINITY。
    • 7、NEGATIVE_INFINITY 除以 NEGATIVE_INFINITY 或 POSITIVE_INFINITY 是 NaN。
    • 8、任何数除以 NEGATIVE_INFINITY 为 0。
  • Number.POSITIVE_INFINITY 属性表示正无穷大。Number.POSITIVE_INFINITY 的值同全局对象 Infinity 属性的值相同。
    • 该值的表现同数学上的无穷大有点儿不同:
    • 1、任何正值,包括 POSITIVE_INFINITY,乘以 POSITIVE_INFINITY 为 POSITIVE_INFINITY。
    • 2、任何负值,包括 NEGATIVE_INFINITY,乘以 POSITIVE_INFINITY 为 NEGATIVE_INFINITY。
    • 3、0 乘以 POSITIVE_INFINITY 为 NaN。
    • 4、NaN 乘以 POSITIVE_INFINITY 为 NaN。
    • 5、POSITIVE_INFINITY 除以 NEGATIVE_INFINITY 以外的任何负值为 NEGATIVE_INFINITY。
    • 6、POSITIVE_INFINITY 除以 POSITIVE_INFINITY 以外的任何正值为 POSITIVE_INFINITY。
    • 7、POSITIVE_INFINITY 除以 NEGATIVE_INFINITY 或 POSITIVE_INFINITY 为 NaN。
    • 8、任何数除以 POSITIVE_INFINITY 为 0。
  • Number.NaN 表示“非数字”(Not-A-Number)。和 NaN 相同。
console.log(Number.NEGATIVE_INFINITY); // -Infinity
console.log(-Number.NEGATIVE_INFINITY); // Infinity
console.log(Number.NEGATIVE_INFINITY + 100); // -Infinity
console.log(typeof Number.NEGATIVE_INFINITY); // number
console.log(Number.POSITIVE_INFINITY * Number.NEGATIVE_INFINITY); // -Infinity
console.log(Number.NEGATIVE_INFINITY * Number.NEGATIVE_INFINITY); // Infinity
console.log(0 * Number.NEGATIVE_INFINITY); // NaN
console.log(0 / Number.NEGATIVE_INFINITY); // -0
console.log(0 - Number.NEGATIVE_INFINITY); // Infinity
console.log(-1 - Number.NEGATIVE_INFINITY); // Infinity
console.log(Number.NaN / Number.NEGATIVE_INFINITY); // NaN
console.log(Number.NaN * Number.NEGATIVE_INFINITY); // NaN
console.log(Number.NaN + Number.NEGATIVE_INFINITY); // NaN
console.log(Number.NEGATIVE_INFINITY / Number.NaN); // NaN
console.log(Number.NEGATIVE_INFINITY * Number.NaN); // NaN
console.log(Number.NEGATIVE_INFINITY + Number.NaN); // NaN
console.log(Number.NEGATIVE_INFINITY / Number.NEGATIVE_INFINITY); // NaN
console.log(Number.NEGATIVE_INFINITY * Number.NEGATIVE_INFINITY); // Infinity
console.log(Number.NEGATIVE_INFINITY / -100); // Infinity
console.log(Number.NEGATIVE_INFINITY / Number.POSITIVE_INFINITY); // NaN
console.log(Number.NEGATIVE_INFINITY * Number.POSITIVE_INFINITY); // -Infinity
console.log(Number.NEGATIVE_INFINITY / 100); // -Infinity
console.log(1 / Number.NEGATIVE_INFINITY); // -0

Number 的一些实例方法

Number.prototype.toExponential(fractionDigits)

以指数表示法返回该数值字符串表示形式。

  • fractionDigits:一个整数,用来指定小数点后有几位数字。默认情况下用尽可能多的位数来显示数字。
  • 对数值字面量使用 toExponential() 方法,且该数值没有小数点和指数时,应该在该数值与该方法之间隔开一个空格,以避免点号被解释为一个小数点。也可以使用两个点号调用该方法。
  • 如果一个数值的小数位数多余 fractionDigits 参数所提供的,则该数值将会在 fractionDigits 指定的小数位数处四舍五入。
  • 如果 fractionDigits 太小或太大将会抛出该错误。介于 0 和 20(包括 20)之间的值不会引起 RangeError 。 执行环境也可以支持更大或更小范围。
var numObj = 77.1234;
console.log('numObj.toExponential() is ' + numObj.toExponential()); // 输出 7.71234e+1
console.log('numObj.toExponential(4) is ' + numObj.toExponential(4)); // 输出 7.7123e+1
console.log('numObj.toExponential(2) is ' + numObj.toExponential(2)); // 输出 7.71e+1
console.log('77.1234.toExponential() is ' + (77.1234).toExponential()); // 输出 7.71234e+1
console.log('77.toExponential() is ' + (77).toExponential()); // 输出 7.7e+1

Number.prototype.toFixed(digits)

  • 使用定点表示法来格式化一个数值。该数值在必要时进行四舍五入,另外在必要时会用 0 来填充小数部分,以便小数部分有指定的位数。
  • digits:小数点后数字的个数;介于 0 到 20 (包括)之间,实现环境可能支持更大范围。如果忽略该参数,则默认为 0。
  • 如果 digits 参数太小或太大。0 到 20(包括)之间的值不会引起 RangeError。实现环境(implementations)也可以支持更大或更小的值

返回值:使用定点表示法表示给定数字的字符串。

如果数值大于 1e+21,该方法会简单调用 Number.prototype.toString() 并返回一个指数记数法格式的字符串。

var numObj = 12345.6789;
numObj.toFixed(); // 返回 "12346":进行四舍五入,不包括小数部分
numObj.toFixed(1); // 返回 "12345.7":进行四舍五入
numObj.toFixed(6); // 返回 "12345.678900":用0填充
(1.23e20).toFixed(2); // 返回 "123000000000000000000.00"
(1.23e-10).toFixed(2); // 返回 "0.00"
(2.34).toFixed(1); // 返回 "2.3"
-(2.34).toFixed(1); // 返回 -2.3 (由于操作符优先级,负数不会返回字符串)
(-2.34).toFixed(1); // 返回 "-2.3" (若用括号提高优先级,则返回字符串)

由于浮点数的原因,小数 5 的四舍五入是不确定的

(10.055)
  .toFixed(2)(
    // 10.05
    10.005,
  )
  .toFixed(2); // 10.01

重写类似 toFixed 方法

function toFixed(num, s) {
  var rateNum = Math.pow(10, s);
  var des = num * rateNum + 0.5;
  des = parseInt(des, 10) / rateNum;
  return des + '';
}

Number.prototype.toPrecision(precision)

  • 以指定的精度返回该数值对象的字符串表示。
  • 1、precision:可选。一个用来指定有效数个数的整数。
  • 2、以定点表示法或指数表示法表示的一个数值对象的字符串表示,四舍五入到 precision 参数指定的显示数字位数。
  • 3、如果忽略 precision 参数,则该方法表现类似于 Number.prototype.toString()。如果该参数是一个非整数值,将会向下舍入到最接近的整数。
  • 4、如果 precison 参数不在 1 和 100 (包括)之间,将会抛出一个 RangeError 。执行环境也可以支持更大或更小的范围。ECMA-262 只需要最多 21 位显示数字。
var numObj = 5.123456;
console.log('numObj.toPrecision()  is ' + numObj.toPrecision()); //输出 5.123456
console.log('numObj.toPrecision(5) is ' + numObj.toPrecision(5)); //输出 5.1235
console.log('numObj.toPrecision(2) is ' + numObj.toPrecision(2)); //输出 5.1
console.log('numObj.toPrecision(1) is ' + numObj.toPrecision(1)); //输出 5

// 注意:在某些情况下会以指数表示法返回
console.log((1234.5).toPrecision(2)); // "1.2e+3"

该方法用于四舍五入时不太可靠,跟浮点数不是精确储存有关。

(12.35)
  .toPrecision(3)(
    // "12.3"
    12.25,
  )
  .toPrecision(3)(
    // "12.3"
    12.15,
  )
  .toPrecision(3)(
    // "12.2"
    12.45,
  )
  .toPrecision(3); // "12.4"

Number.prototype.toString(radix)

返回指定 Number 对象的字符串表示形式。

  • radix: 指定要用于数字到字符串的转换的基数(从2 到 36)。如果未指定 radix 参数,则默认值为 10。
  • 如果 toString() 的 radix 参数不在 2 到 36 之间,将会抛出一个 RangeError。
  • Number 对象覆盖了 Object 对象上的 toString() 方法,它不是继承的 Object.prototype.toString()。对于 Number 对象,toString() 方法以指定的基数返回该对象的字符串表示。
var count = 10;

console.log(count.toString()); // 输出 '10'
console.log((17).toString()); // 输出 '17'
console.log((17.2).toString()); // 输出 '17.2'

var x = 6;

console.log(x.toString(2)); // 输出 '110'
console.log((254).toString(16)); // 输出 'fe'

console.log((-10).toString(2)); // 输出 '-1010'
console.log((-0xff).toString(2)); // 输出 '-11111111'

为什么要带小括号:如果不加括号,这个点会被 JavaScript 引擎解释成小数点,从而报错。

console.log(926.
toString()
)
; // Invalid or unexpected token
console.log((926).toString()); // 926
// JavaScript 会把第一个点理解成小数点(即10.0),把第二个点理解成调用对象属性
console.log(926..toString()); // 926
console.log(926.
toString()
)
; // 926
console.log(926.0.toString()); // 926

Number 的一些静态方法

Number.isFinite()

  • 用来检测传入的参数是否是一个有穷数(finite number)。
  • 返回值:一个布尔值,表示给定的值是否是一个有穷数。
  • 和全局的 isFinite() 函数相比,这个方法不会强制将一个非数值的参数转换成数值,这就意味着,只有数值类型的值,且是有穷的(finite),才返回 true。
Number.isFinite(Infinity); // false
Number.isFinite(NaN); // false
Number.isFinite(-Infinity); // false
Number.isFinite(0); // true
Number.isFinite(2e64); // true
Number.isFinite('0'); // false, 全局函数 isFinite('0') 会返回 true
Number.isFinite(null); // false

// 全局的
isFinite(Infinity); // false
isFinite(NaN); // false
isFinite(-Infinity); // false
isFinite(0); // true
isFinite(2e64); // true
isFinite('0'); // true, 在 Number.isFinite('0')中将会得到false
isFinite(null); // true Number(null) == 0 返回的是true
isFinite(false); // true

Number.isInteger()

  • 用来判断给定的参数是否为整数。
  • 如果被检测的值是整数,则返回 true,否则返回 false。注意 NaN 和正负 Infinity 不是整数.
Number.isInteger(-100000); // true
Number.isInteger(0.1); // false
Number.isInteger(Math.PI); // false
Number.isInteger(Infinity); // false
Number.isInteger('10'); // false
Number.isInteger(false); // false
Number.isInteger([1]); // false

JavaScript 内部,整数和浮点数采用的是同样的储存方法,所以 25 和 25.0 被视为同一个值。

Number.isInteger(25); // true
Number.isInteger(25.0); // true

Number.isNaN()

NaN 详情

Number.isSafeInteger()

  • 用来判断传入的参数值是否是一个“安全整数”
  • 安全整数范围为 -(253 - 1) 到 253 - 1 之间的整数,包含 -(253 - 1) 和 253 - 1。
Number.isSafeInteger(3); // true
Number.isSafeInteger(Math.pow(2, 53)); // false
Number.isSafeInteger(Math.pow(2, 53) - 1); // true
Number.isSafeInteger(NaN); // false
Number.isSafeInteger(Infinity); // false
Number.isSafeInteger('3'); // false
Number.isSafeInteger(3.1); // false
Number.isSafeInteger(3.0); // true

Number.EPSILON

Number.EPSILON: 它表示 1 与大于 1 的最小浮点数之间的差。

  • 对于 64 位浮点数来说,大于 1 的最小浮点数相当于二进制的1.00..001,小数点后面有连续 51 个零。这个值减去 1 之后,就等于 2 的 -52 次方。
  • Number.EPSILON 实际上是 JavaScript 能够表示的最小精度。误差如果小于这个值,就可以认为已经没有意义了,即不存在误差了。

作用 引入一个这么小的量的目的,在于为浮点数计算,设置一个误差范围。我们知道浮点数计算是不精确的。

console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 - 0.3); // 5.551115123125783e-17

解决方法,如下代码如果两个数之间的差值小于 Number.EPSILON,我们就认为这两个值是相等的

function numbersCloseEnoughToEqual(n1, n2) {
  return Math.abs(n1 - n2) < Number.EPSILON;
}

console.log(numbersCloseEnoughToEqual(0.1 + 0.2, 0.3)); // true;

一些特殊用法

  • 数字前面的 0 可以省略:
var a = 0.926;
var b = 0.926;
console.log(a === b); // true
  • 小数点后小数部分最后面的 0 也可以省略:
var a = 29.0;
var b = 29;
console.log(a === b); // true

数值分隔符

ES2021,允许 JavaScript 的数值使用下划线(_)作为分隔符。

let budget = 1_000_000_000_000;
budget === 10 ** 12; // true

123_00 === 12_300; // true

12345_00 === 123_4500; // true
12345_00 === 1_234_500; // true

1234500 === 1_2_3_4_5_0_0; // true

0.000_221 === 0.000221;

使用注意点

  • 不能放在数值的最前面(leading)或最后面(trailing)。
  • 不能两个或两个以上的分隔符连在一起。
  • 小数点的前后不能有分隔符。
  • 科学计数法里面,表示指数的 e 或 E 前后不能有分隔符。
// 全部报错
3;
_;
0.141;
3;
_141;
1;
_e12;
1;
e_12;
123;
__456;
_1464301;
1464301;
_;

下面三个将字符串转成数值的函数,不支持数值分隔符

  • Number()
  • parseInt()
  • parseFloat()
Number('123_456'); // NaN
parseInt('123_456'); // 123
💬留言板0

还没有留言,来发表第一条吧~

PAGE LOAD / LIVEJavaScript Number 数值类型详解与精度问题加载中