【java】统计图表百分比和如何等于百分之百

最近遇到一个问题,各个类型资金占总资金的百分比,计算后客户端以图饼的形式展示。

方法一:利用java中的Bigdecimal 进行统计

代码如下:

  @Test
    public void test() {
        double a = 5, b = 11, c = 13, d = 22, e = 36;
        long sum = 87;

        BigDecimal bigDecimal = new BigDecimal(a / sum).setScale(2, BigDecimal.ROUND_HALF_EVEN);
        BigDecimal bigDecimal1 = new BigDecimal(b / sum).setScale(2, BigDecimal.ROUND_HALF_EVEN);
        BigDecimal bigDecimal2 = new BigDecimal(c / sum).setScale(2, BigDecimal.ROUND_HALF_EVEN);
        BigDecimal bigDecimal3 = new BigDecimal(d / sum).setScale(2, BigDecimal.ROUND_HALF_EVEN);
        BigDecimal bigDecimal4 = new BigDecimal(e / sum).setScale(2, BigDecimal.ROUND_HALF_EVEN);

        System.out.println(Double.parseDouble(bigDecimal.toString()));
        System.out.println(bigDecimal1);
        System.out.println(bigDecimal2);
        System.out.println(bigDecimal3);
        System.out.println(bigDecimal4);
        BigDecimal add = bigDecimal.add(bigDecimal1).add(bigDecimal2).add(bigDecimal3).add(bigDecimal4);
        System.out.println(add);

    }

在这里插入图片描述

我们发现最后总和还确实为1,可以可以,但是这只是我们在随机情况下进行测试,表面好像没问题,其实有的时候还是会导致精度问题,比如下面这样。

@Test
    public void test1() {
        double a = 3, b = 3, c = 3;
        long sum = 9;

        BigDecimal bigDecimal = new BigDecimal(a / sum).setScale(2, BigDecimal.ROUND_HALF_EVEN);
        BigDecimal bigDecimal1 = new BigDecimal(b / sum).setScale(2, BigDecimal.ROUND_HALF_EVEN);
        BigDecimal bigDecimal2 = new BigDecimal(c / sum).setScale(2, BigDecimal.ROUND_HALF_EVEN);


        System.out.println(Double.parseDouble(bigDecimal.toString()));
        System.out.println(bigDecimal1);
        System.out.println(bigDecimal2);
        BigDecimal add = bigDecimal.add(bigDecimal1).add(bigDecimal2);
        System.out.println(add);

    }

在这里插入图片描述
这个原因是因为 BigDecimal 中 BigDecimal.ROUND_HALF_EVEN 这个静态类型造成的,具体参数意义如下:

ROUND_CEILING 
Rounding mode to round towards positive infinity. 
向正无穷方向舍入 

ROUND_DOWN 
Rounding mode to round towards zero. 
向零方向舍入 

ROUND_FLOOR 
Rounding mode to round towards negative infinity. 
向负无穷方向舍入 

ROUND_HALF_DOWN 
Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round down. 
向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,向下舍入, 例如1.55 保留一位小数结果为1.5 

ROUND_HALF_EVEN 
Rounding mode to round towards the "nearest neighbor" unless both neighbors are equidistant, in which case, round towards the even neighbor. 
向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,如果保留位数是奇数,使用ROUND_HALF_UP ,如果是偶数,使用ROUND_HALF_DOWN 


ROUND_HALF_UP 
Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round up. 
向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,向上舍入, 1.55保留一位小数结果为1.6 


ROUND_UNNECESSARY 
Rounding mode to assert that the requested operation has an exact result, hence no rounding is necessary. 
计算结果是精确的,不需要舍入模式 


ROUND_UP 
Rounding mode to round away from zero. 
向远离0的方向舍入

其实这样的话并不是我们的效果,因为客户端想要展示的是百分百,如果是对于要求不严的,可以用这种方法去写。

方法二 差减法

这个方法就是我先计算前 n - 1 个,然后在用 1 去减前面的和。
但是这里有个问题,就是我们是一个 double 的,那么可能减后的结果就变成了 3.999999999 这样的形式,所以还需要处理,思路比较简单,也就不黏贴了。

方法三 递增加一法

这个是我参考的谷歌的一个帖子的思路:http://www.dovov.com/100-9.html
但不知为什么排版很混乱,不过思路还是很不错的。

现在我们将资金的各个百分比计算出

13.626332% 47.989636% 9.596008% 28.788024%

我们拿到整数部分和小数部分

整数部分

13 47 9 28 总和为97

小数部分

.626332% .989636% .596008% .788024%

我们依次比对小数部分哪个最大,然后将对应的整数部分加一。
现在是.989636% 最大,那么将47+1=48,
现在总和为 13 + 48 + 9 + 28 =98,还不够 100,
我们在去小数部分中找第二大的,.788024% ,将此对应的整数部分加一,
28 + 1 = 29,
现在的总和为 13 + 48 + 9 + 29 =99,
发现还是不够一百,那么在找小数部分第三大的,.626332% ,对应的整数部分为13 + 1 = 14,
现在的总和为 14 + 48 + 9 + 29 =100,
那么我们返回的结果就为 14, 48, 9, 29

代码:

@Test
public void test2() {
        List<Long> list = new ArrayList<>();
        list.add(3522L);
        list.add(15000L);
        list.add(220L);
        list.add(14562L);
        list.add(555L);
        list.add(666L);
        list.add(120L);
        List<Integer> list1 = listStatistics(list);
        System.out.println(list1);
}
    
 public List<Integer> listStatistics(List<Long> curList) {
        if (curList.size() == 0) {
            return null;
        }
        double sum = 0;

        for (Long aLong : curList) {
            sum += aLong;
        }

        List<Integer> integerList = new ArrayList<>();
        List<Double> doubleList = new ArrayList<>();
        for (Long aLong : curList) {
            double v = aLong / sum * 100;
            int vInt = (int) v;
            integerList.add(vInt);
            doubleList.add(v - vInt);
        }

        int curSum = 0;
        for (int i = 0; i < curList.size(); i++) {
            curSum += integerList.get(i);
        }

        while (curSum < 100) {

            int index = 0;
            for (int i = 1; i < doubleList.size(); i++) {
                if (doubleList.get(i) > doubleList.get(index)) {
                    index = i;
                }
            }
            Integer integer = integerList.get(index);
            integerList.set(index, ++integer);
            doubleList.set(index, 0.0);
            curSum++;
        }

        return integerList;
    }

在这里插入图片描述

有时候我们需要的数据为27.8这样的,其实思路还是一样的,就是我们扩大1000 倍,然后最后在除以 10就好。

在这里插入图片描述