java拆包算法_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 切分成指定个数指定精度指定范围的若干个数

/**

* 将一个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);

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值