背景:
由于各种原因需要后端来计算每个项所占的百分比,但是会发现计算的各项百分比合计不绝对是100,不能简单的对各项使用四舍五入法,舍九法,进一法等。
思路:
使用最大余额法(比例代表制投票制度下,一种议席分配的方法,结尾有描述)
举个具体例子就很好理解:总计29个员工给3个组投票,请计算出各组所占的百分比,精确到整数。
常规计算出百分比如下:
组名 | 票数 | 计算值(%) | 精确到整数 | 小数余额 |
---|---|---|---|---|
A组 | 10 | 34.482..... | 34 | 0.482..... |
B组 | 12 | 41.379..... | 41 | 0.379..... |
C组 | 7 | 24.137..... | 24 | 0.137..... |
但是很明显,计算值存在小数,产品经理要求精确到整数,且最终的百分比合计又需要等于100,还剩下最后一个数分给谁?
按照最大余额法,应该将最后一个数分给小数余额最大的A组(0.482.....)。同理,如果多出两个数,第二多出的数应该分给小数余额第二大的组。
使用java编码实现如下:
为了方便观察,封装了实体类:
@Data
@NoArgsConstructor
public class PercentVo {
/**
* 数量
*/
private int number;
/**
* 百分比
*/
private int percent;
/**
* 小数点后的余额,不需要返回给前端
*/
@JSONField(serialize = false)
private Double point;
/**
* 组名
*/
private String name;
public PercentVo(int number, String name) {
this.number = number;
this.name = name;
}
}
方法及测试类:
public class NumberUtils {
/**
* 最大余额法,精度为整数
*
* @param list
*/
public static void calculatePercent(List<PercentVo> list) {
//求和:总票数
int sum = list.stream().mapToInt(PercentVo::getNumber).sum();
for (PercentVo percentVo : list) {
double value = percentVo.getNumber() * 100 / (double) sum;
int valueInt = (int) value;
//设置对应的百分比
percentVo.setPercent(valueInt);
//获取小数点后的值
percentVo.setPoint(value - valueInt);
System.out.println("percentVo = " + percentVo);
}
//求和:当前各项百分比合计。由于我们舍弃了小数位,所以该合计只会小于等于100
int curSum = list.stream().mapToInt(PercentVo::getPercent).sum();
while (curSum < 100) {
//找出小数余额最大的组,对其进行加1
PercentVo max = list.stream().max(Comparator.comparingDouble(PercentVo::getPoint)).get();
max.setPercent(max.getPercent() + 1);
//当前这个数已经加1了,不应该参与下一轮的竞选
max.setPoint(0.0);
curSum++;
}
}
public static void main(String[] args) {
PercentVo v1 = new PercentVo(10, "A组");
PercentVo v2 = new PercentVo(12, "B组");
PercentVo v3 = new PercentVo(7, "C组");
List<PercentVo> list = Arrays.asList(v1, v2, v3);
System.out.println("处理前--------------------------");
calculatePercent(list);
System.out.println("处理后--------------------------");
for (PercentVo percentVo : list) {
System.out.println(percentVo);
}
}
}
结果打印如下:
处理前--------------------------
percentVo = PercentVo(number=10, percent=34, point=0.48275862068965836, name=A组)
percentVo = PercentVo(number=12, percent=41, point=0.3793103448275872, name=B组)
percentVo = PercentVo(number=7, percent=24, point=0.137931034482758, name=C组)
处理后--------------------------
PercentVo(number=10, percent=35, point=0.0, name=A组)
PercentVo(number=12, percent=41, point=0.3793103448275872, name=B组)
PercentVo(number=7, percent=24, point=0.137931034482758, name=C组)
扩展:这里为了方便,仅做了精确到整数,还可以扩展为:添加入参2,自定义精度。来提高适用性,思路是一样的。
延申:
最大余额方法(英文:Largest Remainder Method)又称数额制,是比例代表制投票制度下,一种议席分配的方法,相对于最高均数方法。
透过最大余额方法,候选人须以名单参选,每份名单的人数最多可达至相关选区内的议席数目。候选人在名单内按优先次序排列。选民投票给一份名单,而不是个别候选人。投票结束后,把有效选票除以数额。一份名单每取得数额1倍的票数,便能获分配一个议席。每份名单的候选人按原先订立的顺序当选。
如此类推、将议席分配至每份名单的余额,均比数额为低的时候,则从最大余额者顺序分配余下议席;最大余额方法因而得名。