浅谈JavaScript中小数和大整数的精度丢失
先来看两个问题:
0.1+0.2==0.3;//false
9999999999999999==10000000000000000;//true
第一个问题是小数的精度问题,在业界不少博客里已有讨论。第二个问题,去年公司有个系统的数据库在做数据订正时,发现有部分数据重复的诡异现象。本文将从规范出发,对上面的问题做个小结。
最大整数
JavaScript中的数字是用IEEE754双精度64位浮点数来存储的,其格式为:
sxmx2^e
s是符号位,表示正负。m是尾数,有52bits.e是指数,有11bits.在ECMAScript规范里有给出e的范围为[-1074,971].这样,很容易推导出JavaScript能表示的最大整数为:
1x(2^53-1)x2^971=1.7976931348623157e+308
这个值正是Number.MAX_VALUE
同理可推导出Number.MIN_VALUE的值为:
1x1x2^(-1074)=5e-324
注意MIN_VALUE表示最接近0的正数,而不是最小的数。最小的数是-Number.MAX_VALUE
小数的精度丢失
JavaScript的数字都是双精度浮点数,在计算机里用二进制存储。当有效位数超过52位时,会存在精度丢失。比如:
十进制0.1的二进制为0.0001100110011…(循环0011)
十进制0.2的二进制为0.001100110011…(循环0011)
0.1+0.2相加可表示为:
e=-4;m=1.10011001100...1100(52位)
+e=-3;m=1.10011001100...1100(52位)
---------------------------------------------
e=-3;m=0.11001100110...0110
+e=-3;m=1.10011001100...1100
---------------------------------------------
e=-3;m=10.01100110011...001
---------------------------------------------
=0.01001100110011...001
=0.30000000000000004(十进制)
根据上面的演算,还可以得出一个结论:当十进制小数的二进制表示的有限数字不超过52位时,在JavaScript里是可以精确存储的。比如:
0.05+0.005==0.055//true
进一步的规律,比如:
0.05+0.2==0.25//true
0.05+0.9==0.95//false
需要考虑IEEE754的Roundingmodes,有兴趣的可进一步研究。
大整数的精度丢失
这个问题鲜有人提及。首先得弄清楚问题是什么:
1.JavaScript能存储的最大整数是什么?
该问题前面已回答,是Number.MAX_VALUE,非常大的一个数。
2.JavaScript能存储的且不丢失精度的最大整数是什么?
根据sxmx2^e,符号位取正,52位尾数全填充1,指数e取最大值971,显然,答案依旧是Number.MAX_VALUE.
我们的问题究竟是什么呢?回到起始代码:
9999999999999999==10000000000000000;//true
很明显,16个9还远远小于308个10.这个问题与MAX_VALUE没什么关系,还得归属到尾数m只有52位上来。
可以用代码来描述:
varx=1;//为了减少运算量,初始值可以设大一点,比如Math.pow(2,53)-10
while(x!=x+1)x++;
//x=9007199254740992即2^53
也就是说,当x小于等于2^53时,可以确保x的精度不会丢失。当x大于2^53时,x的精度有可能会丢失。比如:
x为2^53+1时,其二进制表示为:
10000000000...001(中间共有52个0)
用双精度浮点数存储时:
e=1;m=10000..00(共52个0,其中1是hiddenbit)
显然,这和2^53的存储是一样的。
按照上面的思路可以推出,对于2^53+2,其二进制为100000…0010(中间51个0),也是可以精确存储的。
规律:当x大于2^53且二进制有效位数大于53位时,就会存在精度丢失。这和小数的精度丢失本质上是一样的。
hiddenbit可参考:AtutorialaboutJavadoubletype.
小结
小数和大整数的精度丢失,并不仅仅在JavaScript中存在。严格来说,使用了IEEE754浮点数格式来存储浮点类型的任何编程语言(C/C++/C#/Java等等)都存在精度丢失问题。在C#、Java中,提供了Decimal、BigDecimal封装类来进行相应的处理,才避开了精度丢失。
注:ECMAScript规范中,已有 decimalproposal,但目前尚未被正式采纳。
最后考考大家:
Number.MAX_VALUE+1==Numer.MAX_VALUE;
Number.MAX_VALUE+2==Numer.MAX_VALUE;
...
Number.MAX_VALUE+x==Numer.MAX_VALUE;
Number.MAX_VALUE+x+1==Infinity;
...
Number.MAX_VALUE+Number.MAX_VALUE==Infinity;
//问题:
//1.x的值是什么?
//2.Infinity-Number.MAX_VALUE==x+1;是true还是false?
以上这篇浅谈JavaScript中小数和大整数的精度丢失就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。