用一句话概括就是:
EcmaScrpt规范定义Number的类型遵循了IEEE754-2008中的64位浮点数规则定义的小数后的有效位数至多为52位导致计算出现精度丢失问题!
如果你看不懂这句话,仔细阅读本篇博客就对了!
首先看下10进制转换为2进制的方法。
数字逻辑电路上的算法是 (0.1)10 = (0.0)2。
吐槽一句,大二的专业课数字逻辑电路终于用在工作上了。
0.1*2 = 0.2 ,整数位为0,且精度只到十分位,因此是0.0。
如果是不限精度的话,转换后的二进制数应该是:0.000110011001100110011(0011)无限循环。
如果表示成一个奇怪的形式,则是 (-1)^0*1.100110011(0011)* 2^-4
上述式子可类比十进制科学计数法公式。
0.0001234567 = 1.234567 * 10^-4
为什么要这样表示?
-1的0次幂又是什么意思?
这是国际标准组织IEEE754对于浮点数表示方式的一种定义。
格式为;
(-1)^S x Mx 2^E各符号的意思如下:
S,是符号位,决定正负,0时为正数,1时为负数。
M,是指有效位数,大于1小于2。
E,是指数位。
因此才有了下面的形式:
(-1)^0*1.100110011(无限循环0011) * 2^-4
S = 0,M = 1.100110011(无限循环0011),E =-4
对应的0.2为:
(-1)^0*1.100110011(无限循环0011) * 2^-3
S = 0 ,M = 1.100110011(无限循环0011),E =-3
那么这和javascript有什么关系呢?
因为IEEE754标准里,还有两种特殊的定义。
IEEE 754规定,对于32位的浮点数,最高的1位是符号位S,接着的8位是指数E,剩下的23位为有效数字M。
对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
问题还是一样,这和我们的javascript有什么关系呢?
因为javascript中Number类型,就是严格按照IEEE754标准来定义的。下面给出了最新版的ecma-262版本中关于Number类型的定义。
*6.1.6
The Number Type**
The Number type has exactly 18437736874454810627 (that is, ) values, representing the double-precision 64-bit format IEEE 754-2008 values as specified in the IEEE Standard for Binary Floating-Point Arithmetic, except that the 9007199254740990 (that is, ) distinct “Not-a-Number” values of the IEEE Standard are represented in ECMAScript as a single special value.
再看一下wiki百科给出的IEEE754标准:
因此,javascript的Number类型, 最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
拿0.1举例来说:
(-1)^01.100110011(无限循环0011) 2^-4
S = 0,M = 1.100110011(无限循环0011),E =-4
这里的无限循环就有限了,循环位数最多只能有52位.
JS中的0.1,在引擎中运算时,实质上会编译成:
1.1001100110011001100110011001100110011001100110011001*2^-4
0.2同理,会编译成:
1.1001100110011001100110011001100110011001100110011001*2^- 3
拿出关键的指数部分和有效位部分:
-4 0.1001100110011001100110011001100110011001100110011001 ①
-3 0.1001100110011001100110011001100110011001100110011001 ②·
①式转化为纯小数,小数最低位的1001被高位的0000挤出有效范围,得到③式
②式转化为纯小数,小数最低位的001被高位的000挤出有效范围,得到④式
原因是什么?
原因就是JS中的Number类型,二进制小数的有效位数只有52位,从0到51位(包括边界)。
在chrome控制台输入(0.1).toString('2')
并打印结果为:"0.0001100110011001100110011001100110011001100110011001101"
不多不少,小数部分刚好52位,与规范以及我们的猜想完全契合。
回到0.1+0.2===0.30000000000000004这个经典问题。
在EcmaScript中,无关Browser环境,还是Nodejs环境,0.1+0.2的实际计算过程如下:
0.0000100110011001100110011001100110011001100110011001 ③
+0.0001001100110011001100110011001100110011001100110011 ④
---------------------------------------------------------------------------------------------------
=0.0100110011001100110011001100110011001100110011001100 ⑤
最后得到的⑤式其实0.300000000000000004(17位十进制数)的二进制形式。
这就是0.1+0.2 ===0.300000000000000004的原因。
虽然我们期望的理想结果是返回0.3,恰恰印证了现实往往很骨感的说法。
有没有让0.1+02返回为0.3的办法呢?
因为不只是这一个精度丢失特例,还有很多情况都会造成精度丢失,比如:
0.3 / 0.1===2.9999999999999996以及0.7 * 180==125.99999999998等等。
那么有没有办法解决这个问题呢?
移步下一篇博客:如何解决0.1 +0.2===0.30000000000000004类问题
鸣谢单位:
https://segmentfault.com/a/1190000005022170
http://demon.tw/copy-paste/javascript-precision.html
http://www.ruanyifeng.com/blog/2010/06/ieee_floating-point_representation.html
http://www.css88.com/archives/7340#more-7340
http://www.ecma-international.org/ecma-262/8.0/index.html
https://en.wikipedia.org/wiki/Floating-point_arithmetic#Internal_representation
期待和大家交流,共同进步,欢迎大家加入我创建的与前端开发密切相关的技术讨论小组:
- SegmentFault技术圈:ES新规范语法糖
- SegmentFault专栏:趁你还年轻,做个优秀的前端工程师
- 知乎专栏:趁你还年轻,做个优秀的前端工程师
- Github博客: 趁你还年轻233的个人博客
- 前端开发QQ群:660634678
- 微信公众号: 人兽鬼 / excellent_developers
努力成为优秀前端工程师!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。