一组数据百分比的优化算法(js)
在图表计算百分比时,经常遇到百分比的和不等于1。比如一组数据[1,1,1],计算的百分比结果为 [33%, 33%, 33%],百分比的和为99%,不等于1。计算百分比保留位数时经常需要四舍五入,从而产生误差,同时可能会出现百分比整体不为1的情况。我们可以依次调整误差较大的百分比,使百分比总和为1。以[3, 1, 5, 9]这组数据为例,具体步骤如下:计算出各项百分比。
0: 17%;1: 6%;2: 28%;3: 50%计算百分比的和。
17% + 6% + 28% + 50% = 101%101% > 1,超出1%。可以通过保留百分比的更多位数来计算每个百分比的误差。然后根据误差大小排序。
排序前:
[0]:17%,[1]:6%,[2]:28%,[3]:50%
计算误差:
[0]:17% - 16.67% = 0.33%
[1]:6% - 5.56% = 0.44%,
[2]:28% - 27.78% = 0.22%,
[3]:50% -50.00% = 0.00%
排序后:[1]:6%,[0]:17%,[2]:28%,[3]:50%
百分比总和超出了1%,排序后的百分比依次减少最小精度值(1%),减少超出值,直到百分比总和等于1。(该示例中只超出了1%,只需减少第1个值即可)
[1]:6% - 1% = 5% 超出0%,百分比总和为1,结束。
[0]:17%,
[2]:28%,
[3]:50%
5)最后结果为:
[0]:17%,[1]:5%,[2]:28%,[3]:50%
js的代码实现:
/*** @param { Array } arr 一组数据* @param { Boolean } needSign 返回值是否添加%* @param { Number } decimalCount 精度:最多保留多少位小数* @returns { Array }*/
const calcPercentage = (arr = [], needSign = true, decimalCount = 4) => {
let sum = arr.reduce((a, b) => a + b, 0),
D = decimalCount < 2 ? 2 : decimalCount,
// E1, E2将小数转化成整数,解决因精度导致小数计算错误的问题 E1 = Math.pow(10, D),
E2 = Math.pow(10, D + 2);
if(sum === 0) {
return arr.map(_ => needSign ? '0%' : 0);
}
const perArr = arr.map((e, i) => ({
index: i,
percentage: Math.round((e / sum).toFixed(D) * E1)
}));
const all = perArr.reduce((a, b) => a + b.percentage, 0);
// 比较计算出的百分比总和和实际总和 if(all > E1) {
// 根据误差大小排序 perArr.sort((a, b) => {
const aCur = a.percentage * Math.pow(10, 2),
aReal = Number((arr[a.index] / sum).toFixed(D + 2)) * E2,
bCur = b.percentage * Math.pow(10, 2),
bReal = Number((arr[b.index] / sum).toFixed(D + 2)) * E2,
aError = aCur - aReal,
bError = bCur - bReal;
return bError - aError < 0 ? -1 : bError - aError > 0 ? 1 : a.index - b.index; // 根据误差排序,如果误差一样,索引小的在前面 });
let sumError = all - E1, i = 0;
// 百分比总和多的部分,从误差大的百分比依次扣除 while(sumError) {
perArr[i].percentage = perArr[i].percentage - 1;
sumError--;
i++;
}
}
if(all < E1) {
debugger;
// 根据误差大小排序 perArr.sort((a, b) => {
const aCur = a.percentage * Math.pow(10, 2),
aReal = Number((arr[a.index] / sum).toFixed(decimalCount + 2)) * E2,
bCur = b.percentage * Math.pow(10, 2),
bReal = Number((arr[b.index] / sum).toFixed(decimalCount + 2)) * E2,
aError = aCur - aReal,
bError = bCur - bReal;
return aError - bError < 0 ? -1 : aError - bError > 0 ? 1 : a.index - b.index;
});
let sumError = all - E1, i = 0;
// 百分比总和少的部分,从误差大的百分比依次补上 while(sumError) {
perArr[i].percentage = perArr[i].percentage + 1;
sumError++;
i++;
}
}
return perArr.sort((a, b) => a.index - b.index).map(e => needSign ? (e.percentage / Math.pow(10, D - 2)).toFixed(D - 2) + '%' : e.percentage/E1);
};