吐槽:原本知识想写个求和的封装,结果
0.1 + 0.2 !== 0.3// 这是在js中遇到的很神奇也很烦的问题 到后来用扩大相同倍数成整数再相加又遇到1.001*1000!==1001,不得不得加法好难
Tip:想要结果的直接请调到最后!
原因
首先我们需要知道:
- Javascript采用了
IEEE-745浮点数表示法 - 在计算机中都是以
二进制进行存储
所以我们再来看看:0.1 + 0.2
先把 0.1 和 0.2 转换成二进制看看: 0.1 => 0.0001 1001 1001 1001…(无限循环) 0.2 => 0.0011 0011 0011 0011…(无限循环)
而双精度浮点数的小数部分最多支持 52 位,所以两者相加之后得到这么一串 0.0100110011001100110011001100110011001100110011001100
因浮点数小数位的限制而截断的二进制数字,这时候,我们再把它转换为十进制,就成了 0.30000000000000004
结果就会产生在控制台打印:0.1 + 0.2 === 0.3 会得到 false
首先想到的是将所有小数均乘10的备注,从而转为整数再相加,所以有了第一版解决方案
解决方案(假的)
基本都是将浮点数全部转为整数后相加,再对相加后的数除以10的n次方进行还原, 以下进行了简单的封装,需求不同的朋友可以参考进行别的扩展
let sum1 = [0.01, 0.2] // 直接 0.01 + 0.2 会得到 0.21000000000000002
/**
* 对浮点数求和
* @param {Array} [sumArr] 加数数组
* @return {Number} 返回加数数组相加之和
* */
function sum(sumArr) {
// 获取所有加数的小数位数
let floatArr = sumArr.map(numItem => numItem.toString().split('.')[1] ? numItem.toString().split('.')[1].length :
0)
// 获取所有加数中小数位数最长的位数
let maxFloat = Math.max(...floatArr)
// 根据最长位数得到新的加数数组
let newSumArr = sumArr.map(numItem => numItem * 10 ** maxFloat)
// 计算出新的加数数组之和
let sumNumber = 0
newSumArr.forEach(num => {
sumNumber += num
});
return sumNumber / 10 ** maxFloat
}
console.log(sum(sum1)) // 0.21
本来以为按照加数的小数位数进行相乘相加会没有问题,但是直到我用[1.001, 2.002]参数进行测试打印的结果尽然为3.0029999999999997,后来发现主要是因为在处理numItem * 10 ** maxFloat的时候,1.001*1000===1000.9999999999999说明乘以小数的位数暂时行不通,换个思路将加数转为整数字符串再相加减
解决方案(真的)
function sum(sumArr) {
// 获取所有加数的小数位数
let floatArr = sumArr.map(numItem => numItem.toString().split('.')[1] ? numItem.toString().split('.')[1].length :
0)
// 获取所有加数中小数位数最长,最小的位数
let maxFloat = Math.max(...floatArr)
// 将所有数字转为不含小数点的整数(扩大相同倍数)
let sumStrArr = sumArr.map((numItem, numIndex) => {
// 转为不含小数点的整数
let num = numItem.toString().replace('.', '') - 0
// 保证所有数扩大相同倍数
num *= 10 ** (maxFloat - floatArr[numIndex])
return num
})
// 缩写
// let sumStrArr = sumArr.map((numItem, numIndex) => (numItem.toString().replace('.', '') - 0) * 10 ** (maxFloat - floatArr[numIndex]))
// 计算出新的加数数组之和
let sumNumber = 0
sumStrArr.forEach(num => {
sumNumber += num
});
return sumNumber / 10 ** maxFloat
}
测试:
sum([0.1, 0.2]) // 0.3
sum([1.001, 2.002]) // 3.003
sum([1, 2.002]) // 3.002
sum([1.01, 2.0002]) // 3.0102
解决方案(简化版)
这个简化版就要从一次找四舍五入的方法说起了,在javascript中四舍五入采用的是银行家算法,但是银行家算法并不是我们日常生活中所需要的四舍五入,找着找着然后的然后就发现了一个有趣的东西e,所以简化版的就出来了(有能更简洁的方法的话欢迎私聊)
function sum(sumArr) {
// 获取所有加数的小数位数
let floatArr = sumArr.map(numItem => numItem.toString().split('.')[1] ? numItem.toString().split('.')[1].length :
0)
// 获取所有加数中小数位数最长的位数
let maxFloat = Math.max(...floatArr)
// 将所有数字扩大相同倍数
let sumStrArr = sumArr.map((numItem, numIndex) => Math.round(`${numItem}e${maxFloat}`))
// 计算出新的加数数组之和
let sumNumber = sumStrArr.reduce((num1, num2) => num1 + num2);
return Number(`${sumNumber}e-${maxFloat}`)
}