BigDecimal 拆分 Java实现微信发红包算法

原代码地址
http://18810098265.iteye.com/blog/2369857;
需要实现一个小功能, 就是把一个BigDecimal对象拆分成若干个,保证总和一致,最好能指定精度(几位小数).

想了想这不就是一个类似微信发红包的功能么,于是就顺着这个思路找了找,最后找到博主的这个.注释清楚,实现易懂,稍微修改一下就可以支持任意精度,不仅限于发红包了,修改后的代码如下:

import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collections;
import java.util.List;

@Slf4j
public class RedBagUtils {

    /**
     * 每个红包最小金额,单位为与scale有关,例如scale为2,单位为分
     */
    private static final int MIN_MONEY = 1;

    /**
     * 红包金额的离散程度,值越大红包金额越分散
     */
    private static final double DISPERSE = 1;

    /**
     * 根据剩余的总量和总个数,获取一个随机的量
     *
     * @param amount 总量
     * @param count  总个数
     * @return 随机量
     */
    public static BigDecimal getOneRedBag(BigDecimal amount, int count, int scale) {
        //pow函数是个计算 10的scale次方的函数 
        int money = amount.setScale(scale, RoundingMode.HALF_UP).multiply(BigDecimalUtils.pow(10, scale)).intValue();
        if (money < MIN_MONEY * count) {
            log.error("amount={}, count={},最小值设置过大", amount.toPlainString(), count);
            throw new RuntimeException("最小值设置过大");
        }

        //最大值 = 均值*离散程度
        int max = (int) (money * DISPERSE / count);

        //最大值不能大于总金额
        max = max > money ? money : max;
        return new BigDecimal(randomBetweenMinAndMax(money, count, MIN_MONEY, max)).divide(BigDecimalUtils.pow(10, scale), scale,
                RoundingMode.HALF_UP);

    }

    /**
     * 在最小值和最大值之间随机产生一个
     *
     * @param money
     * @param count
     * @param min   : 最小量
     * @param max   : 最大量
     * @return
     */
    public static int randomBetweenMinAndMax(int money, int count, int min, int max) {
        //最后一个直接返回
        if (count == 1) {
            return money;
        }
        //最小和最大金额一样,返最小和最大值都行
        if (min == max) {
            return min;
        }
        //最小值 == 均值, 直接返回最小值
        if (min == money / count) {
            return min;
        }
        //min<=随机数bag<=max
        int bag = ((int) Math.rint(Math.random() * (max - min) + min));

        //剩余的均值
        int avg = (money - bag) / (count - 1);
        //比较验证剩余的还够不够分(均值>=最小值 是必须条件),不够分的话就是最大值过大
        if (avg < MIN_MONEY) {
            /*
             * 重新随机一个,最大值改成本次生成的量
             * 由于 min<=本次金额bag<=max, 所以递归时bag是不断减小的。
             * bag在减小到min之间一定有一个值是合适的,递归结束。
             * bag减小到和min相等时,递归也会结束,所以这里不会死递归。
             */
            return randomBetweenMinAndMax(money, count, min, bag);
        } else {
            return bag;
        }
    }


    public static void main(String[] args) {
        //总量
        BigDecimal amount = new BigDecimal(1);
        //总个数
        int count = 10;

        //最后这个数要和amount一致才对
        BigDecimal total = new BigDecimal(0);
        List list = Lists.newArrayList();
        for (int i = 0; i < count; i++) {
            BigDecimal tem = getOneRedBag(amount.subtract(total), count - i, 4);
            total = total.add(tem);
            list.add(tem);
            System.out.println("第" + (count - i) + "个红包的金额是:" + tem + "元");
        }
        //总金额是否相等
        System.out.println("总计金额是否相等:" + (total.compareTo(amount) == 0));
        System.out.println("红包个数:" + list.size());
        System.out.println("红包金额明细:" + list);
        Collections.sort(list);
        System.out.println("排序后的红包明细:" + list);
    }


}

说下大体思路:

  1. 定义两个常量, 最小值和离散系数.最小值很好理解,离散系数干嘛用的呢?离散系数用来算随机时的最大值, 最大值=离散系数*平均值.
  2. 把BigDecimal对象都转成int去做计算比较,这样更直观易懂.
  3. 既然确定了最大值最小值, 那就每次在此区间随机出一个值即可,唯一需要担心的是此次随机出来的值是否过大,后面还够不够分,这里作者采用递归的方式来控制.
  4. 最后一次直接返回剩余的即可.

最后我在此基础上又改了一下, 可以将一个BigDecimal 切分成指定个数指定精度指定范围的若干个数

    /**
     * 将一个BigDecimal 切分成指定个数指定精度指定范围的若干个数,如果范围设置不当,前面随机的数较小,可能会导致最后一个数过大超出范围
     * @param amount 总数量
     * @param count 总个数
     * @param scale 精度
     * @param min 最大值
     * @param max 最小值
     * @return
     */
    public static List splitBigDecimalFromRange(BigDecimal amount, int count, int scale, BigDecimal min, BigDecimal max) {
        BigDecimal total = new BigDecimal(0);
        List list = Lists.newArrayList();
        for (int i = 0; i < count; i++) {
            BigDecimal tem = getOneRedBag(amount.subtract(total), count - i, scale, min, max);
            total = total.add(tem);
            list.add(tem);
        }
        return list;
    }


    private static BigDecimal getOneRedBag(BigDecimal amount, int count, int scale, BigDecimal min, BigDecimal max) {
        // 转成int做运算
        int amountInt = amount.setScale(scale, RoundingMode.HALF_UP).multiply(BigDecimalUtils.pow(10, scale)).intValue();
        int minInt = min.setScale(scale, RoundingMode.HALF_UP).multiply(BigDecimalUtils.pow(10, scale)).intValue();
        int maxInt = max.setScale(scale, RoundingMode.HALF_UP).multiply(BigDecimalUtils.pow(10, scale)).intValue();
        if (amountInt < minInt * count) {
            throw new RuntimeException("最小值设置过大");
        }
        if (minInt > maxInt){
            throw new RuntimeException("最大值小于最小值");
        }
        if (maxInt * count < amountInt){
            throw new RuntimeException("最大值设置过小");
        }
        //最大值不能大于总金额
        maxInt = maxInt > amountInt ? amountInt : maxInt;
        //randomBetweenMinAndMax()上面有这个方法
        return new BigDecimal(randomBetweenMinAndMax(amountInt, count, minInt, maxInt)).divide(BigDecimalUtils.pow(10, scale), scale,
                RoundingMode.HALF_UP);
    }

你可能感兴趣的:(BigDecimal 拆分 Java实现微信发红包算法)