js浮点精度问题
JS浮点精度问题
打开浏览器控制台,进行小数计算
0.1 + 0.2 == 0.3
false
0.1 + 0.2 === 0.3
false
0.1 + 0.2 > 0.3
true
0.1 + 0.2
0.30000000000000004
可以看到,0.1+0.2是不等于0.3,而是大于0.3。
浮点数相加出现这种不相等的情况,是因为其计算过程中存在进制换算问题。
整数转换为二进制
整数换算为二进制是除以2取余,一直除到商为0,然后倒着取余数
比如将24转换为二进制:
//比如24转换为二进制
24/2 = 12余0
12/2 = 6余0
6/2 = 3余0
3/2 = 1余1
1/2 = 0余1
最后倒着取余数,得到24的二进制数
11000
控制台验证一下,没问题。
(24).toString(2)
"11000"
小数转换为二进制
小数换算为二进制则是乘以2取整,最后得到的二进制数是一个无限循环小数,因为计算机存储的空间是有限的,不可能保存一个无限长的数,因此会出现类似”四舍五入“的情况。
比如将0.1转换为二进制:
0.1*2 = 0.2 //无整数,取 0
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
0.4*2 = 0.8 //无整数,取 0
0.8*2 = 1.6 //有整数,取 1
0.6*2 = 1.2 //有整数,取 1
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.1的二进制数(是一个无限循环小数)
0.0001100110011...
控制台验证一下
(0.1).toString(2)
"0.0001100110011001100110011001100110011001100110011001101"
可以看到,按规律,最后一位应该是0。但是在0后的一位是1,那么就往前进一位,类似四舍五入。
因此,JS中0.1取到的二进制数,会比实际上的数要大一点点。0.2转换为二进制也是一样。
计算时,将0.1和0.2转换为二进制数相加,最后将相加的二进制数转换为十进制是0.30000000000000004,当然会比0.3要大了。
JS安全整数
JS中只有一种类型数,即64位(1bit 的符号位,11bits 的指数部分 ,以及52bits 的小数部分)双精度浮点数,当整数数值过大时,就会发生精度丢失。
JS中的安全数
JS中的安全数范围是
(-2**53,2**53)
超过这个范围,就会发生精度丢失,那么计算结果就会不准确。
比如2的53次方结果如下
2**53
9007199254740992
但是
2**53+1
9007199254740992
2**53+2
9007199254740994
2**53+3
9007199254740996
可以看到,计算的结果已经不对了。
JS中能表示的最大最小数
JS中的能表示的最大最小数范围是
(-2**1024+1,2**1024-1)
控制台
2**1024-1
Infinity
//最大数是计算不出来的
//但可以验证它
2**1023*1.999999999999999
1.797693134862315e+308
//小数位再加一个9呢
2**1023*1.9999999999999999
Infinity
toPrecision 和 toFixed
它们的共同点是把数字转成字符串供展示使用。注意在计算的中间过程不要使用,只用于最终结果。
toPrecision 是处理精度,精度是从左至右第一个不为0的数开始数起。
toFixed 是小数点后指定位数取整,从小数点开始数起。
toFixed实际上不是采用的四舍五入,而是四舍六入。有的也叫银行家舍入,简单来说就是:四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一(ps:这一句网上扒来的)。不过也不完全符合银行家舍入规则,经过实践呢,“四舍六入五考虑,五后非零就进一”这一句是正确的,而“五后为零看奇偶,五前为偶应舍去,五前为奇要进一”这一句就得看浏览器了,在IE11上是正确的而在chrome、firefox、safari、opera上就不对了。
需要注意的是,用 toFixed 来做四舍五入是有bug的。
比如
1.005.toFixed(2)
"1.00"
得到的不是1.01,而是1.00
这是因为1.005 实际对应的数字是 1.00499999999999989,在四舍五入时会被被舍去。
小数如何正确计算
数据展示时
当你拿到 1.4000000000000001 这样的数据要展示时,建议使用 toPrecision 凑整并 parseFloat 转成数字后再显示
parseFloat((1.4000000000000001).toPrecision(12)) === 1.4
true
这里12为默认精度,可根据需要进行选择。
数据运算时
上面的方法仅用于数据展示时使用,而不能进行数据运算。
若需要进行精确的加法计算,需要将小数转换为整数后再进行运算
function add(num1, num2) {
//先将小数转换为字符串,然后截取小数部分,计算长度
const num1Digits = (num1.toString().split('.')[1] || '').length;
const num2Digits = (num2.toString().split('.')[1] || '').length;
//Math.pow(4,3)表示4的3次幂 (4*4*4)
//获取小数部分,取其中最大的数作为指数(也就是获取到最大小数位数)
const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits));
//将要相加的小数乘以上一步取到的数,相加,然后再除回来
return (num1 * baseNum + num2 * baseNum) / baseNum;
}
本文系作者 @枫雨 原创发布在枫林幻境站点。未经许可,禁止转载。
暂无评论数据