Appearance
JavaScript中的浮点数
背景
最近在掘金上又看到了一些在讲0.1+0.2为什么不等于0.3的帖子
大多都是只说到浮点数精度损失,具体是怎样也没细讲
本篇博客我们来简单聊聊浮点数
IEEE 754双精度浮点数
首先我们需要知道,JavaScript中的Number类型使用IEEE 754双精度浮点数格式来表示数值
这里引入一篇CSDN博客IEEE754 浮点数:简读+案例=秒懂_ieee754浮点数来简单聊一下IEEE 754双精度浮点数是什么
IEEE 754双精度浮点数由三个部分组成:
类型 | 符号位 | 阶码 | 尾数码 | 总位数 | 偏置值十六进制 | 偏置值十进制 |
---|---|---|---|---|---|---|
短浮点数 | 1 | 8 | 23 | 32 | 7FH | 127 |
长浮点数 | 1 | 11 | 52 | 64 | 3FFH | 1023 |
临时短浮点数 | 1 | 15 | 64 | 80 | 3FFFH | 16383 |
其中:
移码 = 真值 + 偏置值
其中偏置值为2^(n-1)-1
,n为位数
尾数采用示例讲解
0.11 = 1.1 * 2^-1
所以尾数码为1.1000……
,最高位的1
隐藏,所以是1000……
根据这个规则也可以逆推出真值
到这里你可能已经发现了,对于长浮点数而言尾数码只有52位,其实无所谓他有多少位,我们只需要知道他是有限的。
再回忆一下小数的二进制转换,总体就一句话
整数部分除2取余,小数部分乘2取整
对于十进制而言,我们认为10/3
是一个无限循环小数,3.33333……
对于二进制而言也会有一些无限循环小数,比如0.2
ts
0.2*2 = 0.4 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
// 00110011……
1
2
3
4
5
6
2
3
4
5
6
尾数码只有52位,也就是说他的结果只会保留前52位
所以说 0.1 + 0.2 !== 0.3
,小数间运算时精度误差也会累计起来
如果我们需要对浮点数进行比较,也可以指定位数进行判断
ts
Math.abs((0.1 + 0.2) - 0.3) < 1e-10;// true
Math.abs((0.1 + 0.2) - 0.3) < Number.EPSILON;
// Number.EPSILON的值接近2^-52
1
2
3
4
2
3
4
银行家舍入法
银行家舍入法是为了解决传统四舍五入算法的误差发明的算法
在JavaScript中Math.round
和Number.toFixed()
都会进行四舍五入
接下来有以下一个场景
ts
1.35.toFixed(1) === 1.4
// 他从十进制角度来看是正确的
6.35.toFixed(1) === 6.3
// 他从十进制角度来看是错误的
1
2
3
4
2
3
4
这就是传统四舍五入算法的误差,结合上面的双精度浮点数来讲,我们可以知道浮点数的二进制会有精度损失
我们来看看他们的更多小数位是怎样的
ts
6.35.toFixed(20) // 6.34999999999999964473
1.35.toFixed(20) // 1.35000000000000008882
1
2
2
银行家舍入算法总体就一句话
四舍六入五判断
若5
后面非0
,进位后舍去
若5
后面为0
,根据5前数字判断,奇数进位偶数舍去
其实如果你已知舍入位,完全可以在舍入前让其更接近整数,比如
ts
// 6.35->63.5*10^-1
Math.round((6.35*10)/10); // 6.4
1
2
2
63.5
的四舍五入没有任何问题,因为0.5
恰好是2^-1
,这也是一种避免误差的方法
这些浮点数问题不是JavaScript独有的,其他任何语言都会存在,我们需要的不是如何规避,要掌握他并且懂得如何利用他