无论哪种实现方式,随机红包都要遵守几个核心约束(这是和单纯随机数最大的区别):
- 总金额固定(比如 100 元分 10 个红包);
- 每个红包金额非空(至少 0.01 元,避免分到 0);
- 金额随机且符合 "拼手气" 特性(有人多有人少,而非平均);
- 所有红包金额之和严格等于总金额。
php
/**
* 随机分配红包金额.
*
* @param float|string $totalMoney 总金额(元)
* @param int $totalTimes 总次数
* @param float|string $minMoney 单次最小值(元)
* @param float|string $maxMoney 单次最大值(元)
*
* @return array|false 包含每次红包金额的数组(保留两位小数)|false 参数错误时返回false
*/
function random_red_packet($totalMoney, $totalTimes, $minMoney, $maxMoney)
{
// 转换为分进行计算,避免浮点数精度问题
$totalMoneyCent = (int) bcmul($totalMoney, 100);
$minMoneyCent = (int) bcmul($minMoney, 100);
$maxMoneyCent = (int) bcmul($maxMoney, 100);
// 参数合法性校验
if (
$totalTimes <= 0
|| $minMoneyCent <= 0
|| $maxMoneyCent < $minMoneyCent
|| $totalMoneyCent < $totalTimes * $minMoneyCent // 总金额不足以分配最小值
|| $totalMoneyCent > $totalTimes * $maxMoneyCent // 总金额超过最大值总和
) {
return false;
}
$redPackets = [];
$remainingCent = $totalMoneyCent;
$remainingTimes = $totalTimes;
for ($i = 0; $i < $totalTimes; ++$i) {
// 最后一次直接分配剩余金额
if (1 == $remainingTimes) {
$currentCent = $remainingCent;
} else {
// 计算当前可分配的最大金额:不超过单红包最大值,且剩余次数能满足最小值
$maxCurrentCent = min(
$maxMoneyCent,
$remainingCent - ($remainingTimes - 1) * $minMoneyCent
);
// 随机生成当前红包金额(在最小值和最大可分配金额之间)
$currentCent = mt_rand($minMoneyCent, $maxCurrentCent);
}
// 转换为元并保留两位小数
$redPackets[] = bcdiv($currentCent, 100, 2);
// 更新剩余金额和次数
$remainingCent = bcsub($remainingCent, $currentCent, 2);
--$remainingTimes;
}
// 打乱数组(可选,模拟随机顺序)
shuffle($redPackets);
return $redPackets;
}