假设有数组 array = [7, 18, 23, 17]
要求计算每个数字占总和的百分比
普通计算方式
-
计算总和 7 + 18 + 23 + 17 = 65
-
每个数依次除以总和并乘以100
7 / 65 * 100 = 10.76923076923077
18 / 65 * 100 = 27.692307692307693
23 / 65 * 100 = 35.38461538461539
17 / 65 * 100 = 26.153846153846157 -
四舍五入保留两位小数
10.76923076923077 => 10.77
27.692307692307693 => 27.69
35.38461538461539 => 35.38
26.153846153846157 => 26.15
最终结果 10.77 + 27.69 + 35.38 + 26.15 = 99.99 < 100
完整代码
let array = [7, 18, 23, 17]
let sum = array.reduce((total, value) => total + value)
let result = array.map(val => ((val / sum) *100).toFixed(2))
为了解决上面的问题,于是就有了最大余额法,至于什么是最大余额法,百度上有,这里只讲过程
最大余额法
-
计算总和 7 + 18 + 23 + 17 = 65
-
计算总份额
由于计算的是百分比,所以需要扩大100倍
百分比保留两位小数,所以再扩大100倍(三位小数就是1000倍)
所以总份额为 100 * 100 = 10000 -
按比例分配份额
7 / 65 * 10000 = 1076.923076923077
18 / 65 * 10000 = 2769.2307692307693
23 / 65 * 10000 = 3538.461538461539
17 / 65 * 10000 = 2615.3846153846157 -
取分配份额的整数部分 [1076, 2769, 3538, 2615]
1076 + 2769 + 3538 + 2615 = 9998 < 10000
10000 - 9998 = 2 因此还剩两份需要分配 -
取分配份额的小数部分 [0.923076923077, 0.2307692307693, 0.461538461539, 0.3846153846157]
找出小数部分最大的两个数(剩几份找几个),下标分别为0 和 2 -
为整数部分下标为0 和 2的数各加上1
[1076 + 1, 2769, 3538 + 1, 2615] => [1077, 2769, 3539, 2615] -
除以100得到最终的百分比
因为保留2位小数,之前乘以了100,所以最后要除以100
[1077, 2769, 3539, 2615] / 100 => [10.77, 27.69, 35.39, 26.15]
最终结果 10.77 + 27.69 + 35.39 + 26.15 = 100
完整代码
const getPercentValue = (array, precision = 2) => {
// 如果不是数字则赋值为0
let arrayList = array.map(value => (isNaN(value) ? 0 : value))
// 计算总和
let sum = arrayList.reduce((total, value) => total + value)
// 0不能做除数,直接返回
if (sum === 0) return
// 小数位扩大倍数
let digits = Math.pow(10, precision)
// 总份额
let total = digits * 100
// 分配份额
let shareList = arrayList.map(val => (val / sum) * total)
// 取整数部分
let integerList = shareList.map(val => Math.trunc(val))
// 计算整数部分的总和
let shareSum = integerList.reduce((total, val) => total + val)
// 取小数部分
let decimalsList = shareList.map((value, index) => value - integerList[index])
// 整数部分总和小于总份额时
while (shareSum < total) {
let max = decimalsList[0]
let maxIndex = 0
// 找出小数位最大的下标
for (let i = 1; i < decimalsList.length; i++) {
if (decimalsList[i] > max) {
max = decimalsList[i]
maxIndex = i
}
}
// 小数位最大的加1
integerList[maxIndex] += 1
// 加1后小数清零,不参与下次比较
decimalsList[maxIndex] = 0
// 总数也需要加1
shareSum += 1
}
// 返回每项占比
return integerList.map(value => (value / digits).toFixed(precision))
}
网友评论