- 总金额固定(比如 100 元分 10 个红包);
- 每个红包金额非空(至少 0.01 元,避免分到 0);
- 金额随机且符合 “拼手气” 特性(有人多有人少,而非平均);
- 所有红包金额之和严格等于总金额。
/** * 随机分配红包金额. * * @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; }