前些天在项目中遇到一个离奇的问题
"1.38 * 10000 结果竟然是 13799.999999999998"
使出浑身解数后上网搜索了一下,发现大多人说这是 JS 自身的一个 bug。拷贝了一段代码顺利解决问题,但是腾出时间后仔细了解发现问题远不是想象的那样简单。
问题的根本原因归结于 JavaScript 采用IEEE-754
的标准存储数据,而且 JavaScript 只有 Number 一种数字类型,不像其他语言一样有 int、float、decimal 等。所以这就造成了一系列的知名问题0.1+0.2 != 0.3
0.100000000000000002 == 0.1 //true
。
众所周知,计算机采用二进制运算,所以不论是什么类型的数据计算机都会将其转化为二进制进行运算。JavaScript 中所有的 Number 都是以64-bit的双精度浮点数存储的,具体如何存储见下图:
具体的存储原理目前还没有完全弄清楚,在此不做赘述。
大致的原理:双精度浮点数将此64位划分为3段,这3段(64bit)确定一个浮点数的值,IEEE-754
的划分模式为 “1-11-52”,具体描述:
- 第1位(最左位)表示符号位,0代表正,1代表负
- 中间11位标识二进制科学计数法的指数部分
- 最后52位表示尾数部分,也就是有效域部分
&emsp综上可知,指数和尾数域是有限的,11位和52位
&emsp有了以上基础,问题的答案初步浮现出来
精度丢失在哪里
在十进制转化为二进制的时候。例如
0.1+0.2
在开始运算前要将两个数字分别转化为二进制,二者转化后都是无线新循环的。这就超出了IEE-754
规定的52位有效域。这种情况下就要采取规则制定的“舍入规则”,默认的是舍入最接近的值,如果“舍”和“入”一样接近,那么取结果为偶数的选择。浮点计算的时候有一步叫对阶,以加法为例,要把小的指数域转化为大的指数域,也就是左移小指数浮点数的小数点,一旦小数点左移,必然会把52位有效域的最右边的位给挤出去,这个时候挤出去的部分也会发生“舍入”。这就又会发生一次精度丢失。
注意:精度丢失的叠加不一定会使结果偏差越来越大
console.log(0.1) 并不是 0.1
打印的过程其实也是发生了二进制转十进制再转为字符串的过程,所以最后打印出来的并不是对浮点数的精确反应。
所有数值的计算和比较,都是这样以64bit的形式进行的,如果超出位数计算会发行精度丢失;比对会反常,即使是我们明显看出等或者不等的时候。例如1
2
30.100000000000000002 == 0.1 //true
0.100000000000000002 == 0.100000000000000010 // true
例外:+0==-0
解决方案
1 | var calc = { |