Rumah  >  Artikel  >  hujung hadapan web  >  JavaScript中Number类型常见误区背后原理及解决办法

JavaScript中Number类型常见误区背后原理及解决办法

WBOY
WBOYke hadapan
2022-10-09 11:56:341960semak imbas

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于number类型的相关知识,包括了number类型的常见误区背后原理以及解决方法等内容,下面一起来看一下,希望对大家有帮助。

JavaScript中Number类型常见误区背后原理及解决办法

【相关推荐:JavaScript视频教程web前端

在 JavaScript 中数值只有一种,即 Number 类型,内部表示为双精度浮点型,即其他语言中的 double 类型,所以在 JavaScript 中实际上是没有整数类型的,数值都是按浮点数来处理的,存储方法相同,遵循 IEEE 754 国际标准。因此,在 JavaScript 中 3 和 3.0 被视为同一个值:

3.0 === 3 // true

对于整数情况,能够准确计算的整数范围为在−253-2^{53}−253 ~ 2532^{53}253 之间,不包含两个端点,只要在这个范围内整数可以放心使用。除了十进制以外整数还可以通过八进制或十六进制的字面值来表示,其中八进制字面值的第一位必须是零,其次是八进制数字序列(0 ~ 7),如果字面值中的数值超出范围,那么前导零将被忽略,后面的数值被当作十进制解析,这儿需要注意在严格模式中八进制的这种表示会报错,ES6中进一步明确,八进制的表示要使用前缀0o,示例:

(function(){
  console.log(0o11 === 011)
})()
// true
// 严格模式
(function(){
  'use strict';
  console.log(0o11 === 011)
})()
// Uncaught GyntaxError

十六进制字面值前两位必须是 0x,后跟任何十六进制数字(0 ~ 9以及A ~ F),其中 A ~ F 可以大写,也可以小写。ES6中又扩展了二进制的写法,使用前缀0b(或0B)。

前面我们对 JavaScript 运行时中的 Number 类型进行了简单介绍,接下来正式开始介绍这些常见问题,不过首先我们需要了解 Number 类型的数据存储方式:

一、存储方式

JavaScript 中的 Number 类型使用的是双精度浮点型,即其他语言中的 double 类型,双精度浮点数使用 8 个字节即 64bit 来进行存储,现代计算机中浮点数大多是以国际标准 IEEE 754 来存储,存储过程分两步,

  • 把浮点数转换为对应的二进制数,并用科学计数法表示

  • 将转换之后的数通过 IEEE 754 标准表示成真正会在计算机存储的值。

根据 IEEE 754 标准任何一个二进制浮点数 V 都可以表示成:

12.png

13.png

举个例子,十进制的 5.0,写成二进制是 101.0,相当于 1.01∗221.01 * 2^21.01∗22,其中 S=0,M=1.01,E=2。

IEEE 754规定对于32位浮点数最高1位是符号位S,接下来8位是指数E,剩下的23位为有效数字M,具体如下图所示:

14.png

对于64位的浮点数最高1位是符号位S,接下来11位是指数E,剩下的52位是有效数字M,具体如下图所示:

15.png

注意:IEEE754 对于有效数字M和指数E还有一些特别的规定。

前面说过,1 8d566df6b4361e2075cfcf13276e12d0 (00011001100110011001(1001)...)2 (0.2)10 => (00110011001100110011(0011)...)2

可以发现,0.1 和 0.2 转成二进制之后都是一个无限循环的数,前面提到尾数位只能存储最多 53 位有效数字,这时候就必须来进行四舍五入了,而这个取舍的规则就是在 IEEE 754 中定义的,0.1 最终能被存储的有效数字是

0001(1001)(1001)(1001)(1001)(1001)(1001)(1001)(1001)(1001)(1001)(1001)(1001)101 + (0011)(0011)(0011)(0011)(0011)(0011)(0011)(0011)(0011)(0011)(0011)(0011)(0011)01 = 0100(1100)(1100)(1100)(1100)(1100)(1100)(1100)(1100)(1100)(1100)(1100)(1100)111

最终的这个二进制数转换成十进制的就是 0.30000000000000004 ,这儿需要注意,53 位的存储位指的是能存 53 位有效数字,因此前置的 0 不算,要往后再取到  53 位有效数字为止。

因此,精度丢失的问题实际上用一句话概括就是计算机中用二进制存储小数,而大部分小数转成二进制后都是无限循环的值,因此存在取舍问题,也就是精度丢失。

解决办法

ES6 在 Number 对象上新增了一个极小常量:Number.EPSILON,值为 2.220446049250313e-16,引入这么一个常量就是为了为浮点数计算设置一个误差范围,如果这个误差小于 Number.EPSILON 我们就认为得到了准确结果。

三、大整数的运算精度丢失及溢出

问题缘由

在介绍问题的具体缘由之前我想先给大家介绍一下所谓最大安全整数范围以及最大数字绝对值的范围是如何得到的?

JavaScript 能够表示的数字的绝对值范围是 5e-324 ~ 1.7976931348623157e+308,这两个取值可以通过 Number.MIN_VALUE 和 Number.MAX_VALUE 这两个字段来表示,如果某次计算的结果得到了一个超出 JavaScript 数值范围的,那么这个数值会自动被转换为特殊的 Infinity 值,具体来说,如果这个数是负数,则会被转换成 -Infinity(负无穷),如果这个数值是正数,则会被转换成 Infinity(正无穷)。

示例:

console.log(Number.MAX_VALUE) // 1.7976931348623157e+308
console.log(Number.MIN_VALUE) // 5e-324
console.log(Number.MAX_VALUE + Number.MAX_VALUE) // Infinity

那么这个取值范围是如何得到的呢?

前面说到 JavaScript 中数值的保存采用的是双精度浮点型,遵循 IEEE 754 标准,在 ECMAScript 规范中规定指数 E 的范围在 -1074 ~ 971,双精度浮点型中有效数字 M 的存储位为52,但是有效数字 M 由于可以省略第一位1,节省一个存储位,因此有效数字M可以存储的范围为 1 ~ 2532^{53}253,因此 JavaScript 中 Number 能表示的最大数字绝对值范围是 2−10742^{-1074}2−1074 ~ 253+9712^{53+971}253+971。

注:通过 Number.isFinite()(ES6引入)和 isFinite() 方法可以判断一个数值是不是有穷的,即如果参数位于最小与最大数值之间时会返回 true。

让我们回归主题,为什么会出现大整数的运算精度丢失及溢出呢?

JavaScript 中最大安全整数的范围是 −253-2^{53}−253 ~ 2532^{53}253,不包括两个端点,即 -9007199254740991 ~ 9007199254740991,可以通过 Number.MIN_SAFE_INTEGER 和 Number.MAX_SAFE_INTEGER 字段查询,超出这个范围的整数计算都是不准确的,例如:

console.log(Number.MAX_SAFE_INTEGER) // 9007199254740991
console.log(Number.MIN_SAFE_INTEGER) // -9007199254740991
console.log(9007199254740991 + 2) // 9007199254740992

最大安全整数9007199254740991对应的二进制数如图:

16.png

53位有效数字都存储满了之后,想要表示更大的数字,就只能往指数数加一位,这时候尾数因为没有多余的存储空间,因此只能补0。

17.png

如图所示,在指数位为53的情况下,最后一位尾数位为0的数字可以被精确表示,而最后一位尾数位为1的数字都不能被精确表示。也就是可以被精确表示和不能被精确表示的比例是1:1。

同理,当指数为54的时候,只有最后两位尾数为00的可以被精确表示,也就是可以被精确表示和不能被精确表示的比例是1:3,当有效位数达到 x(x>53) 的时候,可以被精确表示和不能被精确表示的比例将是1 : 2^(x-53)^ - 1。

可以预见的是,在指数越来越高的时候,这个指数会成指数增长,因此在 Number.MAX_SAFE_INTEGER ~ Number.MAX_VALUE 之间可以被精确表示的整数可以说是凤毛麟角。

之所以会有最大安全整数这个概念,本质上还是因为数字类型在计算机中的存储结构。在尾数位不够补零之后,只要是多余的尾数为1所对应的整数都不能被精确表示。

可以发现,不管是浮点数计算的计算结果错误和大整数的计算结果错误,最终都可以归结到JS的精度只有53位(尾数只能存储53位的有效数字)。

解决办法

那么我们在日常工作中碰到这两个问题该如何解决呢?

大而全的解决方案就是使用 mathjs,看一下 mathjs 的输出:

math.config({
    number: 'BigNumber',      
    precision: 64 
});
console.log(math.format(math.eval('0.1 + 0.2'))); // '0.3'
console.log(math.format(math.eval('0.23 * 0.34 * 0.92'))); // '0.071944'
console.log(math.format(math.eval('9007199254740991 + 2'))); 
// '9.007199254740993e+15'

【相关推荐:JavaScript视频教程web前端

Atas ialah kandungan terperinci JavaScript中Number类型常见误区背后原理及解决办法. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:juejin.im. Jika ada pelanggaran, sila hubungi admin@php.cn Padam