浮点数运算

你使用的编程语言并不烂,它能够做浮点数运算,但是计算机天生只能存储整数,因此它需要某种方法来表示小数,这种表示方式会带来某种程度的误差,这就是为什么往往 0.1 + 0.2 不等于 0.3

为什么?

实际上很简单:

对于十进制数值系统,它只能表示以进制数的质因子为分母的分数:
10 的质因子有 25。因此 $\frac{1}{2}$、$\frac{1}{4}$、$\frac{1}{5}$、$\frac{1}{8}$ 和 $\frac{1}{10}$ 都可以精确表示,因为这些分母只使用了10的质因子。相反,$\frac{1}{3}$、$\frac{1}{6}$ 和 $\frac{1}{7}$ 都是循环小数,因为它们的分母使用了质因子 3 或者 7
二进制下,只有一个质因子,即 2。因此你只能精确表示分母质因子是 2 的分数。二进制中,$\frac{1}{2}$、$\frac{1}{4}$ 和 $\frac{1}{8}$ 都可以被精确表示。但是,$\frac{1}{5}$ 或者 $\frac{1}{10}$ 就变成了循环小数。所以,在十进制中能够精确表示的 0.10.2($\frac{1}{10}$ 与 $\frac{1}{5}$),到了计算机所使用的二进制数值系统中,就变成了循环小数。当你对这些循环小数进行数学运算时,并将二进制数据转换成人类可读的十进制数据时,会对小数尾部进行截断处理

示例

举个最简单的例子,在 Javascript(大部分编程语言) 中 计算 $0.1 + 0.2$ 会等于 $0.30000000000000004$

在 JS 中数字采用的 IEEE 754 的双精度标准进行存储

解决

想要解决也不是不行,因为计算机存储整数很好运算,所以只要把浮点数转换成整数即可,这里我贴一段 dalao 的代码:

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
var accAdd = function(num1, num2) {
num1 = Number(num1);
num2 = Number(num2);
var dec1, dec2, times;
try { dec1 = countDecimals(num1)+1; } catch (e) { dec1 = 0; }
try { dec2 = countDecimals(num2)+1; } catch (e) { dec2 = 0; }
times = Math.pow(10, Math.max(dec1, dec2));
// var result = (num1 * times + num2 * times) / times;
var result = (accMul(num1, times) + accMul(num2, times)) / times;
return getCorrectResult("add", num1, num2, result);
// return result;
};

var accSub = function(num1, num2) {
num1 = Number(num1);
num2 = Number(num2);
var dec1, dec2, times;
try { dec1 = countDecimals(num1)+1; } catch (e) { dec1 = 0; }
try { dec2 = countDecimals(num2)+1; } catch (e) { dec2 = 0; }
times = Math.pow(10, Math.max(dec1, dec2));
// var result = Number(((num1 * times - num2 * times) / times);
var result = Number((accMul(num1, times) - accMul(num2, times)) / times);
return getCorrectResult("sub", num1, num2, result);
// return result;
};

var accDiv = function(num1, num2) {
num1 = Number(num1);
num2 = Number(num2);
var t1 = 0, t2 = 0, dec1, dec2;
try { t1 = countDecimals(num1); } catch (e) { }
try { t2 = countDecimals(num2); } catch (e) { }
dec1 = convertToInt(num1);
dec2 = convertToInt(num2);
var result = accMul((dec1 / dec2), Math.pow(10, t2 - t1));
return getCorrectResult("div", num1, num2, result);
// return result;
};

var accMul = function(num1, num2) {
num1 = Number(num1);
num2 = Number(num2);
var times = 0, s1 = num1.toString(), s2 = num2.toString();
try { times += countDecimals(s1); } catch (e) { }
try { times += countDecimals(s2); } catch (e) { }
var result = convertToInt(s1) * convertToInt(s2) / Math.pow(10, times);
return getCorrectResult("mul", num1, num2, result);
// return result;
};

var countDecimals = function(num) {
var len = 0;
try {
num = Number(num);
var str = num.toString().toUpperCase();
if (str.split('E').length === 2) { // scientific notation
var isDecimal = false;
if (str.split('.').length === 2) {
str = str.split('.')[1];
if (parseInt(str.split('E')[0]) !== 0) {
isDecimal = true;
}
}
let x = str.split('E');
if (isDecimal) {
len = x[0].length;
}
len -= parseInt(x[1]);
} else if (str.split('.').length === 2) { // decimal
if (parseInt(str.split('.')[1]) !== 0) {
len = str.split('.')[1].length;
}
}
} catch(e) {
throw e;
} finally {
if (isNaN(len) || len < 0) {
len = 0;
}
return len;
}
};

var convertToInt = function(num) {
num = Number(num);
var newNum = num;
var times = countDecimals(num);
var temp_num = num.toString().toUpperCase();
if (temp_num.split('E').length === 2) {
newNum = Math.round(num * Math.pow(10, times));
} else {
newNum = Number(temp_num.replace(".", ""));
}
return newNum;
};

var getCorrectResult = function(type, num1, num2, result) {
var temp_result = 0;
switch (type) {
case "add":
temp_result = num1 + num2;
break;
case "sub":
temp_result = num1 - num2;
break;
case "div":
temp_result = num1 / num2;
break;
case "mul":
temp_result = num1 * num2;
break;
}
if (Math.abs(result - temp_result) > 1) {
return temp_result;
}
return result;
};

dalao 把四则运算封装成了函数,直接使用即可:

1
2
3
4
5
6
7
8
加法: accAdd(0.1, 0.2)  // 得到结果:0.3
减法: accSub(1, 0.9) // 得到结果:0.1
除法: accDiv(2.2, 100) // 得到结果:0.022
乘法: accMul(7, 0.8) // 得到结果:5.6

countDecimals()//方法:计算小数位的长度
convertToInt()//方法:将小数转成整数
getCorrectResult()//方法:确认我们的计算结果无误,以防万一