0
点赞
收藏
分享

微信扫一扫

解决金额按照比例分配除不尽的问题

闲嫌咸贤 2022-03-12 阅读 19
java

1、问题背景

有电商领域,用户对订单支付完成,我们会对订单进行分账,会按照预定的规则与比例,将订单金额分账到:供应商账户、平台账户、营销人员佣金账户,这就涉及到对金额按照比例分配的问题。对金额进行分配,我们要解决除不尽的问题,金额不能除不尽,否则,账户会不平。

   例如,对1.00按照3、3、3的比例分配,则会得到结果0.34、0.33、0.33

2、源码

package cn.vetech.charge.cloud.modules.utils.number;

import java.math.BigDecimal;
import java.math.RoundingMode;

import org.apache.commons.lang3.ArrayUtils;

/**
 * 金额工具类,对金额进行分配,解决除不尽,一分钱的问题,单元测试:MoneyUtilTest
 * @author LiQiao
 * @date 2022/03/07
 */
public final class MoneyUtil {
    private MoneyUtil() {}

    /**
     * 将amount等分为targets等份并保留两位
     *
     * 将金额尽可能平均分配成targets份。如果不能平均分配尽,则将零头放到开始的若干份中。分配运算能够确保不会丢失金额零头。
     *
     * @param amount 待拆分金额
     * @param targets 待分配的份数
     * @return 拆分金额数组, 拆分金额数组的长度与分配份数相同,数组元素从大到小排列,金额最多只相差1分。
     */
    public static BigDecimal[] allocate(BigDecimal amount, int targets) {
        return allocate(amount, targets, 2);
    }

     /**
     * 将amount等分为targets等份并保留scale位小数
     *
     * 将金额尽可能平均分配成targets份。如果不能平均分配尽,则将零头放到开始的若干份中。分配运算能够确保不会丢失金额零头。
     *
     * @param amount 待拆分金额
     * @param targets 待分配的份数
     * @param scale 保留多少位小数
     * @return 拆分金额数组, 拆分金额数组的长度与分配份数相同,数组元素从大到小排列,金额最多只相差1分。
     */
    public static BigDecimal[] allocate(BigDecimal amount, int targets, int scale) {
        long[] ratios = newLongArray(targets);
        return allocate(amount, ratios, scale);
    }


    /**
     * 将amount按照规定的比例分配成若干份并保留2位小数
     *
     * 将金额尽可能平均分配成targets份。如果不能平均分配尽,则将零头放到开始的若干份中。分配运算能够确保不会丢失金额零头。
     *
     * @param amount 待拆分金额
     * @param ratios 分配比例
     * @return 拆分金额数组, 拆分金额数组的长度与分配比例数组相同,数组元素从大到小排列。
     */
    public static BigDecimal[] allocate(BigDecimal amount, long[] ratios) {

        return allocate(amount, ratios, 2);
    }
 /**
     * 将amount按照规定的比例分配成若干份并保留scale位小数
     *
     * 将金额尽可能平均分配成targets份。如果不能平均分配尽,则将零头放到开始的若干份中。分配运算能够确保不会丢失金额零头。
     *
     * @param amount 待拆分金额
     * @param ratios 分配比例
     * @param scale 保留多少位小数
     * @return 拆分金额数组, 拆分金额数组的长度与分配比例数组相同,数组元素从大到小排列。
     */
    public static BigDecimal[] allocate(BigDecimal amount, long[] ratios, int scale) {
        long[] results = new long[ratios.length];
        long sumOfRatios = 0;
        for (int i = 0; i <= ratios.length - 1; i++) {
            sumOfRatios += ratios[i];
        }
        long centOfAll = amount.movePointRight(scale).setScale(0, RoundingMode.HALF_EVEN).longValue();
        long remainder = centOfAll;
        for (int i = 0; i <= results.length - 1; i++) {

            long cent = (centOfAll * ratios[i]) / sumOfRatios;
            results[i] = cent;
            remainder -= results[i];
        }

        for (int i = 0; i < remainder; i++) {
            results[i]++;
        }
        return convert(results, scale);

    }


    private static BigDecimal[] convert(long[] longArray, int scale) {
        if (ArrayUtils.isEmpty(longArray)) {
            return null;
        }
        BigDecimal[] bigDecimalArray = new BigDecimal[longArray.length];
        for (int i = 0; i <= longArray.length - 1; i++) {
            bigDecimalArray[i] = BigDecimal.valueOf(longArray[i], scale);
        }
        return bigDecimalArray;
    }

    private static long[] newLongArray(int targets) {
        long[] longArray = new long[targets];
        for (int i = 0; i <= targets - 1; i++) {
            longArray[i] = 1;
        }
        return longArray;
    }

}

3、单元测试

package cn.vetech.charge.cloud.modules.utils.number;

import java.math.BigDecimal;
import java.util.Arrays;

import org.junit.Assert;
import org.junit.Test;

/**
 * MoneyUtil单元测试
 * @author LiQiao
 * @date 2022/02/18
 */
public class MoneyUtilTest {
    /**
     * 
     * 将1.1分成3等份,保留4位小数,期望结果为:[0.3667, 0.3667, 0.3666]
     * @author LiQiao
     * @throws InterruptedException 
     * @date 2022年1月17日 下午2:40:07
     */
    @Test
    public void allocateTest() {
        BigDecimal amount = new BigDecimal("1.1");
        BigDecimal[] moneyArray = MoneyUtil.allocate(amount, 3,4);
        System.err.println(Arrays.toString(moneyArray));
        Assert.assertTrue(moneyArray.length == 3);
        Assert.assertTrue(moneyArray[0].equals(new BigDecimal("0.3667")));
        Assert.assertTrue(moneyArray[1].equals(new BigDecimal("0.3667")));
        Assert.assertTrue(moneyArray[2].equals(new BigDecimal("0.3666")));

    }

    

}
举报

相关推荐

0 条评论