java计算百分比的最大余额法

1.在项目的过程中我们会经常的去计算一组数中每个数占这一组数的百分比,最近在项目的过程中就会遇到这个问题,一开始我们就简单粗暴的用这个数去除以这一组数的和得到一个保留几位小数的精确值,这种计算方式在大部分的时候是没有问题的,但是也会在大多数情况下产生问题。

最刚开始算法:

NumberFormat numberFormat = NumberFormat.getInstance(); // 设置精确到小数点后2位 numberFormat.setMaximumFractionDigits(2); String result = numberFormat.format((float) num1 / (float) num2 * 100);

这种算法其实很多时候会出现问题,因为是根据精确值计算的,那么就会出现所有数的精确值加起来超过1  或者不足1的现象。

如果 三个数  都是1  还有 3 ,3,5  那么和就是 99.99%   其实这种很多还有会超过100%的 

需求:我们会在Echarts中引入大量的报表,Echarts中只需要把数据填进去就会自动的计算百分比,但是页面也需要展示百分比,以及后台还需要导出Excel。结果我刚开始的时候就用的这种算法去计算的,结果Echats和自己这种方式计算的就存在了一部分差异.最开始我也查了下百度,发现有一种算法,取这个数组中最大的值,让这个最大的值去进行加减操作使其最终的和等于100%。但是一顿操作确实也实现了  最终的结果是100%。但是发现echats官方并不是通过这个算法的.通过官方文档发现是通过最大余额法计算的。

最大余额法的定义可以自寻百度解释。其实就是把这个数按照序数比例进行平分。然后把剩下的那些数在按照余数的最大值进行轮流均分。最大分配的是一个席位。直到把余下的序位分配完毕。

这是我传入的七个数  12 ,11,5,5,3,3,2  通过上面的计算其实可以简单的概括就是

把这一组数和相加得到值为:41    然后 依次除以41*10000  得到一组数。并且保留小数。

2*10000/41=487.804847   3*10000/41 =731.7073170731 ....

然后一次把这得到的余数  拆分:487.804847 :487 和0.804847   731.7073170731 :731和0.7073170731

然后再把整数位相加 487+731+731...  最终结果为9995 

然后再把这些小数放在一个数组中

99995<10000  依次去找这个小数数组的最大值,小数数组最大值对应的整数值加1   依次循环直到最终的和为10000

结束循环。然后再通过这个值去计算百分比。

其实这就是最大余额法思路很简单。

1.这组数和相加 2. 依次每一个数除以这组数和相加的结果  结果乘以10000 

3. 拆分整数位和小数位  4.所有的整数位相加是否小于10000

5.所有小数位最大值对应的整数位值加1  6.循环结束 计算百分比

借助这个思路那么我们就开始写java方法就非常的简单了

public static String getPercentValue(List<Integer> list,int index,int precision){
    if(CollectionUtils.isEmpty(list)){
        return "0";
    }
    //求和
    int listSum = 0;
    //for(int var : list){
        //listSum =add(listSum,var);
    //}
    double listSum = list.stream().mapToDouble(p -> p).sum();
    if(listSum == 0){
        return "0";
    }
    List<Double> seatsList = new ArrayList<>();//整数值
    List<Double> votesPerQuotaList = new ArrayList<>();//求小数得集合
    double currentSum =0;
    //10得二次幂是100用于计算精度
    double targetSeats = Math.pow(10,precision) *100;
    for(int val : list){
        //扩大比例100 用于计算
        //double result = divideToDouble((val * targetSeats),listSum);
        double result = val / sum * targetSeats;
        double seats = Math.floor(result);
        currentSum =add(currentSum,seats);//求总和
        seatsList.add (seats);//取整数位
        votesPerQuotaList.add(subtract(result,seats));//取小数位
    }
    //给最大得值加1 凑够占比100%
    while(currentSum < targetSeats){
        double max =0;int maxId =0;
        for(int i=0; i< votesPerQuotaList.size();i++){
            if(votesPerQuotaList.get(i) > max){
                max = votesPerQuotaList.get(i);
                maxId =i;
            }
        }
        //最大值加1 凑100
        seatsList.set(maxId,add(seatsList.get(maxId),1));
        votesPerQuotaList.set(maxId,0.0);//最大值小数位设为0
        currentSum = add(currentSum,1);
    }
     return calculatePercentage(seatsList.get(index), targetSeats);
}
public static String calculatePercentage(double num1,double num2){
    if(num1 ==0 || num2 ==0){
        return "0";
    }
    NumberFormat numberFormat = NumberFormat.getInstance();
    numberFormat.setMaximumFractionDigits(2);
    return numberFormat.format( num1 / num2 *100)+"";
}
public static double divideToDouble (double num1,int num2){
    if(num1 ==0 || num2 ==0){
        return 0.0f;
    }
    BigDecimal b1 = new BigDecimal(num1);
    BigDecimal b2 = new BigDecimal(num2);
    return b1.divide(b2,8,BigDecimal.ROUND_DOWN).doubleValue();
}
public static double add(double v1, double v2)
{
    BigDecimal b1 = new BigDecimal(Double.toString(v1));
    BigDecimal b2 = new BigDecimal(Double.toString(v2));
    return b1.add(b2).doubleValue();
}
public static double subtract (double num1,double num2){
    BigDecimal b1 = new BigDecimal(num1);
    BigDecimal b2 = new BigDecimal(num2);
    return b1.subtract(b2).doubleValue();
}

当然最大余额法并不一定是满足你的需求的,这个地方计算百分比的时候,最好还是跟客户进行沟通确认,确认过后才能进行这么操作。客户才是最终的上帝。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值