最近有个需求是需要发红包的,正常如果是正常是最小值0.01,那么直接参考网上贴出来的微信红包算法即可。但是这个需求需要设置一个最小值。
private BigDecimal ONE_HUNDRED = BigDecimal.valueOf(100);
private BigDecimal DEF_MIN = BigDecimal.valueOf(0.01);
/**
* 微信-均匀分配,最小值为0.01
*@Author dong
*@Date 2022/5/11 17:10
*@param bean
*@return
*/
private BigDecimal doDivision(Bean bean){
if (bean.remainSize == 1) {
bean.remainSize--;
return bean.remainMoney;
}
BigDecimal max = bean.remainMoney.divide(BigDecimal.valueOf(bean.remainSize),4,BigDecimal.ROUND_DOWN).multiply(BigDecimal.valueOf(2));
BigDecimal money = RandomUtil.randomBigDecimal(max);
money = money.compareTo(DEF_MIN)<0? DEF_MIN: money;
money = money.multiply(ONE_HUNDRED).setScale(0,BigDecimal.ROUND_DOWN).divide(ONE_HUNDRED);
bean.remainMoney = bean.remainMoney.subtract(money);
bean.remainSize--;
return money;
}
@AllArgsConstructor
private class Bean{
BigDecimal remainMoney;
int remainSize;
}
最初想法感觉很简单,这吧最小值改一下不就行了?但运行时发现这只能保证前面的几个数据正常。最后的红包可能会小于最小值甚至被扣成负数!折腾俩小时,终于是保证最小值的同时,又能实现均匀分配。完整代码:
import cn.hutool.core.util.RandomUtil;
import lombok.AllArgsConstructor;
import lombok.experimental.UtilityClass;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
/**
* 分割数字工具
*
* @Author: dong
* @Date: 2022/5/11 11:34
*/
@UtilityClass
public class DivisionNumUtil {
private BigDecimal ONE_HUNDRED = BigDecimal.valueOf(100);
private BigDecimal DEF_MIN = BigDecimal.valueOf(0.01);
public static void main(String[] args) {
System.out.println(division(BigDecimal.valueOf(100), 10));
System.out.println(division(BigDecimal.valueOf(100), 10,BigDecimal.valueOf(9)));
}
/**
* 分割,保留两位小数
*@Author dong
*@Date 2022/5/11 13:42
*@param sum
*@param count
*@return
*/
public List<BigDecimal> division(BigDecimal sum,int count) {
return division(sum,count,null);
}
/**
* 分割,保留两位小数
* 如果sum-min*count<0.01*count,先去出count个min,剩余的随机下标分配
* 否则 先分配出count个min-0.01,然后把剩余的值给再均匀分配
*@Author dong
*@Date 2022/5/11 13:42
*@param sum
*@param count
*@param min 最小值
*@return
*/
public List<BigDecimal> division(BigDecimal sum,int count,BigDecimal min) {
if(sum==null){
throw new IllegalArgumentException("总数不能为空");
}
if(count<=0){
throw new IllegalArgumentException("分割次数不能小于1");
}
if(min==null){
min = DEF_MIN;
}else if(min.compareTo(DEF_MIN)<0){
throw new IllegalArgumentException("最小值不能小于0.01");
}
// 每个元素的基础数值
BigDecimal base;
// 待分配总数值
BigDecimal divisionNum;
// 计算出sum-min*count
divisionNum = sum.subtract(min.multiply(BigDecimal.valueOf(count)));
if(sum.compareTo(min.multiply(BigDecimal.valueOf(count)))<0){
throw new IllegalArgumentException("总数不够分配");
}
List<BigDecimal> result = new ArrayList<>(count);
// 这里算一下,sum-min*count之后,是否还能走微信的均匀分配
if(divisionNum.compareTo(DEF_MIN.multiply(BigDecimal.valueOf(count)))<0){
// 不能微信均匀分配,按最小单位分配到随机下标。基础数值=min
base = min;
for (int i=0;i<count;i++){
result.add(base);
}
// 待分配数
while (divisionNum.compareTo(DEF_MIN)>=0){
divisionNum = divisionNum.subtract(DEF_MIN);
int i = RandomUtil.randomInt(count);
BigDecimal decimal = result.get(i);
result.set(i,decimal.add(DEF_MIN));
}
}else{
// 微信-红包均匀分配,基础数值=min-0.01。
base = min.subtract(DEF_MIN);
// 待分配=sum-基础数值*count
divisionNum = sum.subtract(base.multiply(BigDecimal.valueOf(count)));
Bean bean = new Bean(divisionNum,count);
for (int i=0;i<count;i++){
result.add(doDivision(bean).add(base));
}
}
return result;
}
/**
* 微信-均匀分配,最小值为0.01
*@Author dong
*@Date 2022/5/11 17:10
*@param bean
*@return
*/
private BigDecimal doDivision(Bean bean){
if (bean.remainSize == 1) {
bean.remainSize--;
return bean.remainMoney;
}
BigDecimal max = bean.remainMoney.divide(BigDecimal.valueOf(bean.remainSize),4,BigDecimal.ROUND_DOWN).multiply(BigDecimal.valueOf(2));
BigDecimal money = RandomUtil.randomBigDecimal(max);
money = money.compareTo(DEF_MIN)<0? DEF_MIN: money;
money = money.multiply(ONE_HUNDRED).setScale(0,BigDecimal.ROUND_DOWN).divide(ONE_HUNDRED);
bean.remainMoney = bean.remainMoney.subtract(money);
bean.remainSize--;
return money;
}
@AllArgsConstructor
private class Bean{
BigDecimal remainMoney;
int remainSize;
}
}