BigDecimal|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);
}}
说下大体思路:
- 定义两个常量, 最小值和离散系数.最小值很好理解,离散系数干嘛用的呢?离散系数用来算随机时的最大值, 最大值=离散系数*平均值.
- 把BigDecimal对象都转成int去做计算比较,这样更直观易懂.
- 既然确定了最大值最小值, 那就每次在此区间随机出一个值即可,唯一需要担心的是此次随机出来的值是否过大,后面还够不够分,这里作者采用递归的方式来控制.
- 最后一次直接返回剩余的即可.
/**
* 将一个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);
}
推荐阅读
- java|JAVA-MYSQL-SSH酒店民宿客房管理系统
- java|ssm糖尿病居家健康管理系统,java项目全套代码,小程序+后台管理,mysql数据库
- java|SSM教务管理系统
- java|Java+Swing+Mysql实现ATM自助存取款系统(2022最新版)
- java|使用环信进行即时通信IM
- 即时通讯使用|极光通讯用于java web即时通信---构建idea下maven+springboot第三方即时通讯
- 2022年Java面试总结,200多页真题笔记和详解(含核心考点及6家大厂)
- java|软件架构设计的核心(抽象与模型、“战略编程”)
- Sublime|Sublime Text3编译运行C++/Java/Python程序语言出现中文乱码【已解决】
- Java项目|学习【瑞吉外卖⑥】SpringBoot单体项目_手机验证码登录业务开发