js中,小数加法精度溢出问题(0.1+0.2)

453 阅读2分钟

背景介绍

在控制台中,输入0.1+0.2,输出结果并未按照原有设想输出0.3,而是输出了

原理

原因在于两点:1. 小数加法运算会进行二进制运算。 2. js中,number用64位双精度表示:,1位符号位 11位表示指数位 52位表示数值位

小数二进制转换

文字描述该过程如下:将该数字乘以2,取出整数部分作为二进制表示的第1位;然后再将小数部分乘以2,将得到的整数部分作为二进制表示的第2位;以此类推,直到小数部分为0。

特殊情况: 小数部分出现循环,无法停止,则用有限的二进制位无法准确表示一个小数,这也是在编程语言中表示小数会出现误差的原因。

0.1 * 2 = 0.2 ---- 0

0.2 * 2 = 0.4 ---- 0

0.4 * 2 = 0.8 ---- 0

0.4 * 2 = 0.8 ---- 0

0.8 * 2 = 1.6 ---- 1

0.6 * 2 = 1.2 ---- 1

0.2 * 2 = 0.4 ---- 0

...

因此,0.1跟0.2都被转换成二进制无限小数。

js的Number在内存中怎么存储的

js中,number用64位双精度表示:,1位符号位 11位表示指数位 52位表示数值位

符号位指数位数值位
+/-11位52位

规定:指数位全为1或0时有特殊作用((所以上面,y的范围为(-2^10 + 1) ~ (2^10 - 1),再去掉0和1023,-1023))

问题:什么是指数位,什么是数值位?

回答:首先,需要了解科学计数法,由于数字过大或过小时,就会转换成科学计数法。指数位,表示一个值可以存储科学表示法的数量。
Math.pow(2, 1023) // 8.98846567431158e+307

Math.pow(2, 1023) // Infinity

因此,一个值最大存储起来是2^1023方

数值位,表示一个值可以展示出来的数量。
那么为啥是 53 呢,因为二进制表示中,有效数字最长为53个二进制位( 52 位尾数 + 有效数字第一位的 1[被舍弃的 1] )

举个例子:

2^53 是这么存的:符号位:0,指数:53,尾数:1.00000...000(一共52个0)

2^53 - 1是这么存的:符号位:0,指数:52,尾数:1.11111...111(小数点后52个1)

2^53 - 2的存法:符号位:0,指数:52,尾数:1.11111...110(小数点后51个1,一个0)

总结

0.1 + 0.2: 0.1跟0.2本身是二进制存储,其实是一个无限小数,然后相加,所以,截取52位的双精度浮点数来表示十进制的数字,最后就会出现偏差。