JAVA金额按比例分摊,零头处理

金额精确计算,必须使用BigDecimal;
平均分摊,分摊的零头,一般都是由数据“精度”和分摊系数决定的;
主要是如何对零头进行处理,保证尽可能的平均分配。
1、按户均摊

/**
     * 按户数分摊方式
     * 分摊计算采用截取小数精确位数后的小数值BigDecimal.ROUND_DOWN
     * 目的:指定金额200元,平均分配给6户,分摊前后金额相等
     */
    private static void hsFt() {
        BigDecimal[] repair_shou_Amt = new BigDecimal[6];// 每个分户应承担维修金额
        BigDecimal fact_repair_Amt = new BigDecimal("0");// 维修对象的实际费用(不含零头)

        //待分摊金额
        BigDecimal ft_amt = new BigDecimal("200");
        //分户总数
        int count = repair_shou_Amt.length;

        for(int i = 0; i < count; i++) {
            //求每个分户的承担金额=分摊金额/总户数
            repair_shou_Amt[i] =ft_amt.divide(new BigDecimal(count), 2, BigDecimal.ROUND_DOWN); //截掉精度之后的小数位的值

            //实际分摊承担总金额---小数点截取后的总金额
            fact_repair_Amt = fact_repair_Amt.add(repair_shou_Amt[i]);
        }
        // 零头 = 分摊实际承担金额-待分摊金额
        BigDecimal repair_oddment_amt = fact_repair_Amt.subtract(ft_amt);

        // 算出要参与分摊零头的户数-截掉精度后的小数位的值累加后的每个值假设最大也只可能为0.00999999999999999999无限接近0.01
        int repair_int = repair_oddment_amt.multiply(new BigDecimal("100")).abs().intValue();
        // 把维修金额零头分摊到分户上,如果参与维修零头的户数大于零或者小于总户数,则进行分摊,否则报错
        if( (repair_int < count) && (repair_int > 0) ) {
            // 如果零头小于零,则说明有repair_int个分户承担的金额少一分钱0.01,需要加一分钱
            if( repair_oddment_amt.compareTo(new BigDecimal("0")) < 0 ) {
                for(int i = 0; i < repair_int; i++) {
                    repair_shou_Amt[i] = repair_shou_Amt[i].add(new BigDecimal("0.01"));
                }
            }
            else {
                // 如果零头大于零,则说明有repair_int个分户承担的金额多一分钱0.01,需要减去一分钱
                for(int i = 0; i < repair_int; i++) {
                    repair_shou_Amt[i] = repair_shou_Amt[i].subtract(new BigDecimal("0.01"));
                }
            }
        }
        else if( repair_int == 0 ) {
            // 零头如果为零,不需要处理
        }
        else {
            throw new BusinessException("非法操作!");
        }
    }

2、按面积均摊

/**
     * 按面积分摊方式
     * 分摊计算采用截取小数精确位数后的小数的值BigDecimal.ROUND_DOWN
     * 目的:指定金额200元,平均分配给6户,每户面积可能不等,分摊前后金额相等
     */
    public static void areaFt(){
        BigDecimal[] repair_shou_Amt = new BigDecimal[6];// 每个分户应承担维修金额
        BigDecimal fact_repair_Amt = new BigDecimal("0");// 维修对象的实际费用(不含零头)

        //待分摊金额
        BigDecimal ft_amt = new BigDecimal("200");
        //分户总数
        int count = repair_shou_Amt.length;
        //分户面积
        BigDecimal[]info_area = {new BigDecimal("20.12"),new BigDecimal("10.12"),new BigDecimal("11.12"),new BigDecimal("15.12"),new BigDecimal("10.12"),new BigDecimal("110.12")};

        //分摊的总建面积
        BigDecimal totalArea = ObjectUtil.getZeroBigDecimal();
        for(int i = 0 ; i < count; i++){
            totalArea=totalArea.add(info_area[i]);
        }

        for(int i = 0; i < count; i++) {
            BigDecimal oa_hou_area = info_area[i];
            //求每个分户的承担金额=(分户面积/总建筑面积)*分摊金额
            repair_shou_Amt[i] = (oa_hou_area.multiply(ft_amt).divide(totalArea, 2, BigDecimal.ROUND_DOWN));//截掉精度之后的小数位的值

            //实际分摊承担总金额---小数点截取后的总金额
            fact_repair_Amt = fact_repair_Amt.add(repair_shou_Amt[i]);
        }
        // 零头 = 分摊实际承担金额-待分摊金额
        BigDecimal repair_oddment_amt = fact_repair_Amt.subtract(ft_amt);

        // 算出要参与分摊零头的户数-截掉精度后的小数位的值累加后的每个值假设最大也只可能为0.00999999999999999999无限接近0.01
        int repair_int = repair_oddment_amt.multiply(new BigDecimal("100")).abs().intValue();
        System.out.println(repair_int);
        // 把维修金额零头分摊到分户上,如果参与维修零头的户数大于零或者小于总户数,则进行分摊,否则报错
        if( (repair_int < count) && (repair_int > 0) ) {
            // 如果零头小于零,则说明有repair_int个分户承担的金额少一分钱0.01,需要加一分钱
            if( repair_oddment_amt.compareTo(new BigDecimal("0")) < 0 ) {
                for(int i = 0; i < repair_int; i++) {
                    repair_shou_Amt[i] = repair_shou_Amt[i].add(new BigDecimal("0.01"));
                }
            }
            else {
                // 如果零头大于零,则说明有repair_int个分户承担的金额多一分钱0.01,需要减去一分钱
                for(int i = 0; i < repair_int; i++) {
                    repair_shou_Amt[i] = repair_shou_Amt[i].subtract(new BigDecimal("0.01"));
                }
            }
        }
        else if( repair_int == 0 ) {
            // 零头如果为零,不需要处理
        }
        else {
            throw new BusinessException("非法操作!");
        }
    }

3、按随机均摊

/**
     * 按面积分摊方式
     * 分摊计算采用四舍五入BigDecimal.ROUND_HALF_UP
     * 目的:指定金额200元,平均分配给6户,每户面积可能不等,分摊前后金额相等
     */
    public static void otherFt(){
        BigDecimal[] repair_shou_Amt = new BigDecimal[6];// 每个分户应承担维修金额
        BigDecimal fact_repair_Amt = new BigDecimal("0");// 维修对象的实际费用(不含零头)

        //待分摊金额
        BigDecimal ft_amt = new BigDecimal("200");
        //分户总数
        int count = repair_shou_Amt.length;
        //分户面积
        BigDecimal[]info_area = {new BigDecimal("20.12"),new BigDecimal("10.12"),new BigDecimal("11.12"),new BigDecimal("15.12"),new BigDecimal("10.12"),new BigDecimal("110.12")};

        //分摊的总建面积
        BigDecimal totalArea = ObjectUtil.getZeroBigDecimal();
        for(int i = 0 ; i < count; i++){
            totalArea=totalArea.add(info_area[i]);
        }

        for(int i = 0; i < count; i++) {
            BigDecimal oa_hou_area = info_area[i];
            //求每个分户的承担金额=(分户面积/总建筑面积)*分摊金额
            repair_shou_Amt[i] = (oa_hou_area.multiply(ft_amt).divide(totalArea, 2, BigDecimal.ROUND_HALF_UP));//四舍五入

            //实际分摊承担总金额---小数点四舍五入后的总金额
            fact_repair_Amt = fact_repair_Amt.add(repair_shou_Amt[i]);
        }
        // 零头 = 分摊实际承担金额-待分摊金额
        BigDecimal repair_oddment_amt = fact_repair_Amt.subtract(ft_amt);

        // 算出要参与分摊零头的户数-repair_int这个值的范围是由精度决定的,如果保留两位小数,则有且只可能存在1户
        int repair_int = repair_oddment_amt.multiply(new BigDecimal("100")).abs().intValue();
        System.out.println(repair_int);

        // 把维修金额零头分摊到分户上,如果参与维修零头的户数大于零或者小于总户数,则进行分摊,否则报错
        if( (repair_int < count) && (repair_int > 0) ) {
            //取分摊分户中的某一户随机补或减0.01
            Random rand = new Random();
            int randNum = rand.nextInt(6);
            System.out.println(randNum);

            // 如果零头小于零,则说明有repair_int个分户承担的金额少一分钱0.01,需要加一分钱
            if( repair_oddment_amt.compareTo(new BigDecimal("0")) < 0 ) {
                repair_shou_Amt[randNum] = repair_shou_Amt[randNum].add(new BigDecimal("0.01"));
            }
            else {
                // 如果零头大于零,则说明有repair_int个分户承担的金额多一分钱0.01,需要减去一分钱
                repair_shou_Amt[randNum] = repair_shou_Amt[randNum].subtract(new BigDecimal("0.01"));
            }
        }
        else if( repair_int == 0 ) {
            // 零头如果为零,不需要处理
        }
        else {
            throw new BusinessException("非法操作!");
        }
    }
本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 1

我的算法是这样的:根据每单的金额分配。

package T;

import java.util.ArrayList;
import java.util.List;

public class T7 {
    public static void main(String[] args) {
        System.out.println("start dispatch");

        ArrayList<Integer> testArr = new ArrayList<Integer>();
        testArr.add(20);
        testArr.add(40);
        testArr.add(80);

        // 下面进行单元测试。
        for (int money = 0; money < 141; money++) {

            T7 test = new T7();
            ArrayList<Integer> result = test.dispatch(testArr, money);
            System.out.print("分配后的金额");
            int k=0;
            for (int a : result) {
                System.out.print(a + ",");
                k+= a;

            }
            System.out.print( "总和:="+ k+",优惠券="+money );
            System.out.println();
        }

    }

    /**
     * 主方法,对一个整数列表,把一个金额平均分配在其上面。
     * 
     * @param arr
     * @param realMoney
     * @return
     */
    public ArrayList<Integer> dispatch(ArrayList<Integer> arr, int realMoney) {

        int count = arr.size();
        int total = 0;
        for (int a : arr) {
            total += a;
        }

        ArrayList<Integer> newList = new ArrayList<Integer>();

        if (realMoney > total || total < 1) {
            for (int i = 0; i < count; i++) {
                newList.add(0);
            }
            return newList;
        }

        //System.out.println("开始计算");
        ArrayList<Integer> new2 = new ArrayList<Integer>();

        int i = 0;
        for (int v : arr) {
            // 20 - 1* ( 20 / 140 ) = 20 - 1* 0.014 = 20-1=19
            int temp = v - (int) (Math.ceil(realMoney * ((float)v / (float)total)));
            //System.out.println("原始分配"+temp);
            new2.add(temp);
        }
        int new2Sum = 0;
        for (int a : new2) {
            new2Sum += a;
        }

        if (new2Sum != (total - realMoney)) {

            // 140 - 1 - 
            int diff = total - realMoney - new2Sum;
            ArrayList<Integer> new3  = recursive(arr, new2, diff, 0);
            return new3;
        }
        return new2;

    }

    // 递归分配。
    private ArrayList<Integer> recursive(ArrayList<Integer> old, ArrayList<Integer> newList, int diff, int k) {

        //System.out.println("测试2:diff="+diff+", k="+k );
        if (k == newList.size()) {
            return newList;
        }
        if (diff > 0) {
            //System.out.println("测试");
            if (newList.get(k) != old.get(k)) {
                int min = Math.min(diff, old.get(k) - newList.get(k));
                newList.set(k, newList.get(k) + min);
                diff -= min;
                if (diff != 0) {
                    return recursive(old, newList, diff, k + 1);
                }
            }
        }
        return newList;

    }

}
1年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!