浮点数精度丢失问题

本文共 1,068 字 预计花费 5 分钟

前些天在项目中遇到一个离奇的问题"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的双精度浮点数存储的,具体如何存储见下图:
IEEE-754 64-bit 双精度浮点数存储方式

  具体的存储原理目前还没有完全弄清楚,在此不做赘述。

  大致的原理:双精度浮点数将此64位划分为3段,这3段(64bit)确定一个浮点数的值,IEEE-754 的划分模式为 “1-11-52”,具体描述:

  • 第1位(最左位)表示符号位,0代表正,1代表负
  • 中间11位标识二进制科学计数法的指数部分
  • 最后52位表示尾数部分,也就是有效域部分

 &emsp综上可知,指数和尾数域是有限的,11位和52位

 &emsp有了以上基础,问题的答案初步浮现出来

精度丢失在哪里

  1.   在十进制转化为二进制的时候。例如0.1+0.2在开始运算前要将两个数字分别转化为二进制,二者转化后都是无线新循环的。这就超出了 IEE-754规定的52位有效域。这种情况下就要采取规则制定的“舍入规则”,默认的是舍入最接近的值,如果“舍”和“入”一样接近,那么取结果为偶数的选择。

  2.   浮点计算的时候有一步叫对阶,以加法为例,要把小的指数域转化为大的指数域,也就是左移小指数浮点数的小数点,一旦小数点左移,必然会把52位有效域的最右边的位给挤出去,这个时候挤出去的部分也会发生“舍入”。这就又会发生一次精度丢失。

注意:精度丢失的叠加不一定会使结果偏差越来越大

console.log(0.1) 并不是 0.1

  打印的过程其实也是发生了二进制转十进制再转为字符串的过程,所以最后打印出来的并不是对浮点数的精确反应。

  所有数值的计算和比较,都是这样以64bit的形式进行的,如果超出位数计算会发行精度丢失;比对会反常,即使是我们明显看出等或者不等的时候。例如

1
2
3
0.100000000000000002 == 0.1  //true

0.100000000000000002 == 0.100000000000000010 // true

例外:+0==-0

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
var calc = {
add: function(a,b) {
var c,d,e;
try {
c = a.toString().split(".")[1].length;
} catch (f) {
c = 0;
}

try {
d = b.toString().split(".")[1].length;
} catch (f) {
d = 0;
}

return e = Math.pow(10,Math.max(c,d)),(this.mul(a,e) + this.mul(b,e)) / e;
return c;
},

sub: function(a,b) {
var c,d,e;
try {
c = a.toString().split(".")[1].length;
} catch (f) {
c = 0;
}
try {
d = b.toString().split(".")[1].length;
} catch (f) {
d = 0;
}

return e = Math.pow(10,Math.max(c,d)),(this.mul(a,e) - this.mul(b,e)) / e;
},

mul: function(a,b) {
var c = 0,
d = a.toString(),
e = b.toString();
try {
c +=d.split(".")[1].length;
} catch (f) {}
try {
c += e.split(".")[1].length;
} catch (f) {}
return Number(d.replace(".","")) * Number(e.replace(".","")) / Math.pow(10,c);
},

div: function(a, b) {
var c, d, e = 0,
f = 0;
try {
e = a.toString().split(".")[1].length;
} catch (g) {}
try {
f = b.toString().split(".")[1].length;
} catch (g) {}
return c = Number(a.toString().replace(".", "")), d = Number(b.toString().replace(".", "")), mul(c / d, Math.pow(10, f - e));
}
}

感谢

该死的IEEE-754浮点数,说「约」就「约」,你的底线呢?以JS的名义来好好查查你

Copyright © 2017 - 2018 空脑壳 All Rights Reserved.

冀ICP备17022284号