### 一、问题
在 JavaScript 中整数和浮点数都属于`Number`数据类型,所有数字都是以 64 位浮点数形式储存。 当浮点数做数学运算的时候,你经常会发现一些问题。
```
1. // 加法 =====================
2. 0.1 + 0.2 = 0.30000000000000004
3. 0.7 + 0.1 = 0.7999999999999999
4. 0.2 + 0.4 = 0.6000000000000001
5. 2.22 + 0.1 = 2.3200000000000003
7. // 减法 =====================
8. 1.5 - 1.2 = 0.30000000000000004
9. 0.3 - 0.2 = 0.09999999999999998
11. // 乘法 =====================
12. 19.9 \* 100 = 1989.9999999999998
13. 19.9 \* 10 \* 10 = 1990
14. 1306377.64 \* 100 = 130637763.99999999
15. 1306377.64 \* 10 \* 10 = 130637763.99999999
16. 0.7 \* 180 = 125.99999999999999
17. 9.7 \* 100 = 969.9999999999999
18. 39.7 \* 100 = 3970.0000000000005
20. // 除法 =====================
21. 0.3 / 0.1 = 2.9999999999999996
22. 0.69 / 10 = 0.06899999999999999
```
### 二、原因
在 JavaScript 中计算`0.1 + 0.2`时,0.1 和 0.2 会转成二进制,但由于浮点数用二进制表达时是无穷的。例如
```
1. 0.1 \-> 0.0001100110011001...(无限)
2. 0.2 \-> 0.0011001100110011...(无限)
```
IEEE 754 标准的 64 位双精度浮点数的小数部分最多支持 53 位二进制位,所以两者相加之后得到二进制为:0.0100110011001100110011001100110011001100110011001100,因浮点数小数位的限制而截断的二进制数字,再转换为十进制,就成了`0.30000000000000004`。所以在进行算术计算时会产生误差。
整数也存在类似的问题。
```
1. console.log(19571992547450991); //=> 19571992547450990
2. console.log(19571992547450991\===19571992547450992); //=> true
```
当然这个问题并不只是在 Javascript 中才会出现,几乎所有的编程语言都采用了 IEEE-745 浮点数表示法,任何使用二进制浮点数的编程语言都会有这个问题,只不过在很多其他语言中已经封装好了方法来避免精度的问题,而 JavaScript 是一门弱类型的语言,从设计思想上就没有对浮点数有个严格的数据类型。
### 三、相关问题的解决方案
1、引入库,Math.js decimal.js big.js都能很好解决js计算精度问题。
2、格式化数字,保留二位小数等需求。可以用以下封装的函数
```
function number_format(number, decimals, dec_point, thousands_sep,roundtag) {
/*
* 参数说明:
* number:要格式化的数字
* decimals:保留几位小数
* dec_point:小数点符号
* thousands_sep:千分位符号
* roundtag:舍入参数,默认 "ceil" 向上取,"floor"向下取,"round" 四舍五入
* */
number = (number + '').replace(/[^0-9+-Ee.]/g, '');
roundtag = roundtag || "ceil"; //"ceil","floor","round"
var n = !isFinite(+number) ? 0 : +number,
prec = !isFinite(+decimals) ? 0 : Math.abs(decimals),
sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep,
dec = (typeof dec_point === 'undefined') ? '.' : dec_point,
s = '',
toFixedFix = function (n, prec) {
var k = Math.pow(10, prec);
console.log();
return '' + parseFloat(Math[roundtag](parseFloat((n * k).toFixed(prec*2))).toFixed(prec*2)) / k;
};
s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
var re = /(-?\d+)(\d{3})/;
while (re.test(s[0])) {
s[0] = s[0].replace(re, "$1" + sep + "$2");
}
if ((s[1] || '').length < prec) {
s[1] = s[1] || '';
s[1] += new Array(prec - s[1].length + 1).join('0');
}
return s.join(dec);
}
console.log(number_format(2, 2, ".", ","))//"2.00"
console.log(number_format(3.7, 2, ".", ","))//"3.70"
console.log(number_format(3, 0, ".", ",")) //"3"
console.log(number_format(9.0312, 2, ".", ","))//"9.03"
console.log(number_format(9.00, 2, ".", ","))//"9.00"
console.log(number_format(39.715001, 2, ".", ",", "floor")) //"39.71"
console.log(number_format(9.7, 2, ".", ","))//"9.70"
console.log(number_format(39.7, 2, ".", ","))//"39.70"
console.log(number_format(9.70001, 2, ".", ","))//"9.71"
console.log(number_format(39.70001, 2, ".", ","))//"39.71"
console.log(number_format(9996.03, 2, ".", ","))//"9996.03"
console.log(number_format(1.797, 3, ".", ",", "floor"))//"1.797"
```
number_format 函数可以设置保留几位小数、保留小数取整方向。
3、封装函数解决数学运算问题
```
1. parseFloat((数学表达式).toFixed(digits)); // toFixed() 精度参数须在 0 与20 之间
2. // 运行
3. parseFloat((1.0 - 0.9).toFixed(10)) // 结果为 0.1
4. parseFloat((0.3 / 0.1).toFixed(10)) // 结果为 3
5. parseFloat((9.7 * 100).toFixed(10)) // 结果为 970
6. parseFloat((2.22 + 0.1).toFixed(10)) // 结果为 2.32
```
参考[https://www.html.cn/archives/7340](https://www.html.cn/archives/7340)