Skip to content
目录

JavaScript中的浮点数

背景

最近在掘金上又看到了一些在讲0.1+0.2为什么不等于0.3的帖子

大多都是只说到浮点数精度损失,具体是怎样也没细讲

本篇博客我们来简单聊聊浮点数

IEEE 754双精度浮点数

首先我们需要知道,JavaScript中的Number类型使用IEEE 754双精度浮点数格式来表示数值

这里引入一篇CSDN博客IEEE754 浮点数:简读+案例=秒懂_ieee754浮点数来简单聊一下IEEE 754双精度浮点数是什么

IEEE 754双精度浮点数由三个部分组成:

类型符号位阶码尾数码总位数偏置值十六进制偏置值十进制
短浮点数1823327FH127
长浮点数11152643FFH1023
临时短浮点数11564803FFFH16383

其中:

移码 = 真值 + 偏置值

其中偏置值为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

尾数码只有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

银行家舍入法

银行家舍入法是为了解决传统四舍五入算法的误差发明的算法

在JavaScript中Math.roundNumber.toFixed()都会进行四舍五入

接下来有以下一个场景

ts
1.35.toFixed(1) === 1.4
// 他从十进制角度来看是正确的
6.35.toFixed(1) === 6.3
// 他从十进制角度来看是错误的
1
2
3
4

这就是传统四舍五入算法的误差,结合上面的双精度浮点数来讲,我们可以知道浮点数的二进制会有精度损失

我们来看看他们的更多小数位是怎样的

ts
6.35.toFixed(20) // 6.34999999999999964473
1.35.toFixed(20) // 1.35000000000000008882
1
2

银行家舍入算法总体就一句话

四舍六入五判断

5后面非0,进位后舍去

5后面为0,根据5前数字判断,奇数进位偶数舍去


其实如果你已知舍入位,完全可以在舍入前让其更接近整数,比如

ts
// 6.35->63.5*10^-1
Math.round((6.35*10)/10); // 6.4
1
2

63.5的四舍五入没有任何问题,因为0.5恰好是2^-1,这也是一种避免误差的方法


这些浮点数问题不是JavaScript独有的,其他任何语言都会存在,我们需要的不是如何规避,要掌握他并且懂得如何利用他

Released under the MIT License.