JavaScript的精度丢失和隐式类型转换

最近在网上看到了一张图,觉得很有意思。也算是体现了JS这门弱类型语言的一些“优雅”之处,哈哈哈。

这是图片出处https://elbruno.com/2018/07/01/humor-thanks-for-inventing-javascript/

相信看到这张图,呵呵一笑以外,你绝不会“感谢”创造JS这门语言的人,毕竟看着就很痛苦了。不过还是想说说上面一些情况是为什么。可能有理解不对的地方,希望大家谅解,指正一下。

这篇博客中有些内容在我的博客《JS中的假值》《ECMAScript 2015规范文档中相等(==)运算符的详解》中说过,就不累述了。如果感兴趣的话,可以去看看。

1、typeof NaN //"number"

NaN,即Not a Number,不是一个数字,它本身是Number类型的,所以利用typeof判断类型是自然返回"number"。但是NaN又是一个特殊的Number类型,它永远为假,同时自身也不等于自身。所以有时候在判断一个变量是不是NaN时,可以通过是否等于自身来判断。

function isNaN(value) {
    return !value == value;
}

isNaN(NaN); //true

2、9999999999999999  //10000000000000000

JS针对数值,只有Number类型,采用64bits双浮点精度,如下图所示。

第1位:符号位,0表示正数,1表示负数;

第2位到第12位(共11位):指数部分,0~2047;

第13位到第64位(共52位):小数部分(即有效数字)

因为有效位数只有53位,所以JS能精确表示的最大整数就是Math.pow(2, 53),十进制就是9007199254740992,在JS也设置了Number.MAX_SAFE_INTEGER(最大安全整数)和Number.MIN_SAFE_INTEGER(最小安全整数)。当JS在存储超过9007199254740992的数值时,可能会存在精度丢失的情况(超过52位的会被自动去掉)。例如

以下为摘录阮一峰大牛的文章 

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

- 指数部分一共有11个二进制位,因此大小范围就是0到2047。IEEE 754 规定,如果指数部分的值在0到2047之间(不含两个端点),那么有效数字的第一位默认总是1,不保存在64位浮点数之中。也就是说,有效数字这时总是1.xx...xx的形式,其中xx..xx的部分保存在64位浮点数之中,最长可能为52位。因此,JavaScript 提供的有效数字最长为53个二进制位。

 (-1)^符号位 * 1.xx...xx * 2^指数部分

- 上面公式是正常情况下(指数部分在0到2047之间),一个数在 JavaScript 内部实际的表示形式。

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

3、0.1+0.2 == 0.3 //false

同样的这也是因为JS数值精度丢失的原因导致等于判断为false。JS本身也考虑到了这一点,所以设置一个可接受的误差范围,即Number.EPSILON,当两个值的差值小于等于这个可接受误差范围时,就可以认为这两个数值时相等的。

function isEqual(num1, num2) {
    return Math.abs(num1-num2) <= Number.EPSILON;
}

isEqual(0.1 + 0.2, 0.3); //true

4、Math.min()和Math.max()

查看官方文档可知,当Math.min()方法无参数时返回Infinity,而Math.max()无参数时返回-Infinity。

JS能够表示的最大数值和最小数值分别为Number.MAX_VALUE和Number.MIN_VALUE中,在大多数浏览器中,它们分别是1.7976931348623157e+308和5e-324。可以看到,它们都是正数,是绝对值中的最大和最小数值。

还有比他们更大或者更小的值,当计算结果得到一个超过数值范围的值,那么就会转成Ifinity(正无穷)和-Infinity(负无穷)

JS也提供了2个属性保存这两个无穷值,分别是Number.NEGATIVE_INFINITY(负无穷)和Number.POSITIVE_INFINITY(正无穷)

5、[]+[] []+{}

下面的解释引自《JS高级程序设计第三版》:

- 在使用一元操作符(如+、-、++、--)时,JS存在隐式类型转换。

- a. 在应用于一个包含有效数字字符的字符串时,先将其转换为数字值

- b. 在应用于一个不包含有效数字字符的字符串时,将变量的值设置为NaN

- c. 在应用于布尔值false和true是,分别转换为0和1

- d. 在应用于对象时,先调用对象的valueOf()方法,取的一个可供操作的值。如果结果为NaN,就在调用toString()方式转成字符串,在执行前面的操作。

未被重定义的情况下valueOf() 方法返回指定对象的基础类型值;toString() 方法返回一个表示该对象的字符串。

在这里[]和{}都是引用类型,是对象,所以先调用valueOf方法,后面调用toString方法。

第一个[]+[],Array对象重写了Object的valueOf方法和toString方法,valueOf返回数组本身,toString返回与没有参数(默认为逗号拼接)的 join() 方法返回的字符串相同。所以这里先返回了数组本身,无法进行“+”操作,在调用toString方法,变成了空字符串,两个空字符串相加,所以最后输出空字符串。

第二个[]+{},[]最终转成空字符串,+运算实际上变成了字符串拼接方法,于是{}调用Object的原生toString方法,转成了“[object Object]”,最终拼接为了“[object Object]”。

6、 {}+[]

这个和第五项有点相似,如果按照第五项中的解释去理解,是得不到结果的。为什么呢?这要说到JS引擎本身解释代码的问题了,在JS解释{}时,有两种情况,一种是语句块,一种是对象定义。

当直接在控制台输入{}+[]时,此时解释器将{}解释为语句块,即{};+[],所以输出就变成了+[]的结果,这里+符号会强制转换,执行toNumber()操作,空数组返回数字0。

如果在外面加上括号,即({}+[]),那么{}就会被解释为对象,最后返回“[object Object]”。

7、true+true+true === 3

有运算操作符时,Boolean类型false转为0,true转为1,所以左侧结果为3,值相等,类型也相等,故返回true

8、true - true

和上面同样的理由,转变成数值运算1-1,所以返回0

9、true == 1 

相等操作符,两侧会进行toNumber操作,进行值判断,不进行类型判断。所以1 == 1,返回true

10、true === 1

全等操作符,既判断值,也判断类型,即不做类型转换,这里值虽然相同,一个为Boolean类型,一个为Number类型,所以返回false

11、(! + [] + [] + ![]).length

运算符具有优先级

这里可以看到逻辑非!操作符优先级比+高,所以第一个逻辑非!先执行,相当于!(+[]),+[]进行toNumber操作返回0,然后逻辑非进行toBoolean操作返回true,然后![]执行,于是[]先进行toBoolean的操作,返回true,然后逻辑非操作变成false,然后执行从左至右+运算,即true+[]+false,变成了字符串拼接,于是返回“truefalse”,这个字符串的长度也就是9了

 

--------------------------------------------------- 2018.8.30 更新分割线 ---------------------------------------------------

这一天是很苦逼的一天,文章没写完,结果拼多多笔试就遇到了倒数第二个问题,然而我没想起来!!哭晕,还是知识不够扎实的原因,如果扎实看一眼就应该知道结果的。不过亡羊补牢,为时不晚,把文章写完,给自己涨涨记性。

12、9 + "1" 

无论是9+"1",还是"9"+1,结果都是91。+运算符可以是数字相加运算,也可以是字符拼接运算。但是ES规范规定了,如果+运算符两侧存在字符串时,就调用toString()方法,进行字符串拼接操作,所以这里结果都是91。

13、9 - "1"

-运算符和+运算符不同,因为-运算符就是数字运算减的操作,所以先转成Number类型,所以无论是‘9’ -1 还是9 -‘1’,结果都是8

14、[] == 0

隐式转换,空数组在相等操作符是会转成数字0,所以返回true

 

---------------------------------------------2020.2.10 分割线--------------------

发现一个新大陆

(!(~+[]) + {})[--[~+""][+[]] * [~+[]] + ~~!+[]] + ({} + [])[[~!+[]] * ~+[]]

// 'sb'

才疏学浅,可能会有纰漏,希望大家能指出来。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值