在日常 Java 开发中,业务上可能会遇到一些拉新引流的相关需求,比如:发放红包、签到领现金等(这里以发放红包为例)。
那么就会面临一个问题:怎么保证红包在领取时,金额随机,且至少能领0.01元?
一般会有两种红包方案:
“拼手速”红包
我们假设有一个100元的红包。第一个人可以在0.01元到100元之间,随机地分配到一定金额。
如果我们把第一个人抽到的所有可能的金额都计算在内,并取个平均值,那么他平均能获得50元。
这50元在数学上还有个形象的名字,叫作数学期望。
既然是“期望”,就总会有落空的时候,也不排除会有意外的惊喜。
因此,第一个人抽到的金额可能不足50元,也可能大于50元。
如上所述,虽然每一个人在抢红包时都是随机分配数额,貌似很公平,但总会有“先来后到”。
“雨露均沾”红包
原理其实很简单,就是把每个人可能抽到的最高金额强行降低,即使你是第一个打开红包的人,也不允许你任意的从0.01到100元之间抽取金额。
这时候就需要有个相对“公平”的算法来计算相应金额:二倍均值法
假设红包总金额是X,红包个数为Y,每个红包的最低金额是0.01元
那么每次抢到的红包金额的范围在 (0.01, (X/Y) *2) 之间。
即:每次运算时,在 0.01 ~ (剩余金额 / 剩余个数 * 2) 的范围内随机取值
也就是最大值为:0~100的随机值 / 100 * (剩余金额 / 剩余个数 * 2)
闲话少说,直接上示例代码:
public class ComputeRedPacketAmount {
//二倍均值法:
// 假设红包总金额是X,红包个数为Y,每个红包的最低金额是0.01元,
// 那么每次抢到的红包金额的范围在 (0.01, (X/Y) *2) 之间。
public static void compute(BigDecimal amount, BigDecimal min, BigDecimal num) {
//总金额扣除 (最低金额 * 总个数),也就是预留最低金额
BigDecimal remain = amount.subtract(min.multiply(num));
final Random random = new Random();
final BigDecimal hundred = new BigDecimal("100");
final BigDecimal two = new BigDecimal("2");
BigDecimal sum = BigDecimal.ZERO;
BigDecimal remainNum;
BigDecimal redAmount;
for (int i = 0; i < num.intValue(); i++) {
final int nextInt = random.nextInt(100);
remainNum = num.subtract(new BigDecimal(i));
System.out.println("当前随机值为:" + nextInt);
if (i == num.intValue() - 1) {
//最后一个人分配剩余金额
redAmount = remain;
} else {
// 0~100的随机值 * (剩余金额 * 2 / 剩余个数) / 100
//如: 0~100的随机值 * (100 * 2 / 10) / 100
redAmount = new BigDecimal(nextInt)
.multiply(remain.multiply(two).divide(remainNum, 2, RoundingMode.CEILING))
.divide(hundred, 2, RoundingMode.FLOOR);
System.out.println("随机金额为:" + redAmount);
}
if (remain.compareTo(redAmount) > 0) {
remain = remain.subtract(redAmount);
} else {
remain = BigDecimal.ZERO;
}
sum = sum.add(min.add(redAmount));
System.out.println("第" + (i + 1) + "个人抢红包金额为:" + min.add(redAmount));
System.out.println("剩余金额为:" + remain);
System.out.println("=============================");
}
System.out.println("总共领取的金额为:" + sum);
}
}
测试结果如下:
public class ComputeRedPacketAmount {
public static void main(String[] args) {
BigDecimal amount = new BigDecimal("1");
BigDecimal min = new BigDecimal("0.01");
BigDecimal num = new BigDecimal("10");
compute(amount, min, num);
}
}
当前随机值为:55
随机金额为:0.09
第1个人抢红包金额为:0.10
剩余金额为:0.81
=============================
当前随机值为:82
随机金额为:0.14
第2个人抢红包金额为:0.15
剩余金额为:0.67
=============================
当前随机值为:70
随机金额为:0.11
第3个人抢红包金额为:0.12
剩余金额为:0.56
=============================
当前随机值为:84
随机金额为:0.13
第4个人抢红包金额为:0.14
剩余金额为:0.43
=============================
当前随机值为:88
随机金额为:0.13
第5个人抢红包金额为:0.14
剩余金额为:0.30
=============================
当前随机值为:26
随机金额为:0.03
第6个人抢红包金额为:0.04
剩余金额为:0.27
=============================
当前随机值为:35
随机金额为:0.04
第7个人抢红包金额为:0.05
剩余金额为:0.23
=============================
当前随机值为:12
随机金额为:0.01
第8个人抢红包金额为:0.02
剩余金额为:0.22
=============================
当前随机值为:47
随机金额为:0.10
第9个人抢红包金额为:0.11
剩余金额为:0.12
=============================
当前随机值为:21
第10个人抢红包金额为:0.13
剩余金额为:0
=============================
总共领取的金额为:1.00
原理:
由于有最低领取金额 0.01元,那么先给所有人分0.01元,这时剩余的金额为 amount - num * 0.01
在当前剩余金额中计算随机分配金额:0~100的随机值 / 100 * (剩余金额 / 剩余个数) * 2
得到结果后,再加上已分配的 0.01元,即为最终领取金额
以上就是二倍均值法的相关分析
我是【辛勤de小蜜蜂】关注我,我们下期见
-
由于博主才疏学浅,难免会有纰漏,假如您发现了错误或遗漏的地方,还望留言斧正,我会尽快对其加以修正。
-
如果您觉得文章还不错,您的转发、分享、点赞、留言就是对我最大的鼓励。
-
感谢您的阅读,十分欢迎并感谢您的关注。