本文提供兩種風格截然不同的 PHP 紅包生成函數:循環遞減法與區間切分法,並分析其優缺點、適用場景,最後給出生產環境優化建議。
<?php
/**
* @param float $total 總金額(元)
* @param int $count 紅包數
* @param int $type 1=拼手氣, 0=固定平均
* @return array 紅包金額陣列(元)
*/
function randBonus($total = 0, $count = 3, $type = 1) {
$items = [];
$balance = $total;
$avg = number_format($total / $count, 2);
for ($i = 0; $i < $count; $i++) {
if ($i < $count - 1) {
// 拼手氣:隨機 0.01 ~ (餘額-剩餘最小保障)
// 固定:直接用平均值
$rand = $type ? (rand(1, $balance * 100 - 1) / 100) : $avg;
$items[] = $rand;
$balance -= $rand;
} else {
// 最後一包承擔所有誤差
$items[] = $balance;
}
}
return $items;
}
// 測試
$bonus = randBonus(100, 3, 1);
var_dump($bonus);
var_dump(array_sum($bonus)); // 驗證總和
?>
<?php
function sendRandBonus($total = 0, $count = 3, $type = 1) {
$items = [];
if ($type == 1) {
// 將 [0.01, $total] 以 0.01 為步長離散化
$points = range(0.01, $total, 0.01); // 陣列長度 = $total * 100
// 隨機抽取 $count-1 個切分點
$keys = array_rand($points, $count - 1);
sort($keys);
$last = 0;
foreach ($keys as $k) {
$items[] = $points[$k] - $last;
$last = $points[$k];
}
$items[] = $total - array_sum($items);
} else {
$avg = number_format($total / $count, 2);
for ($i = 0; $i < $count; $i++) {
$items[] = ($i < $count - 1) ? $avg : ($total - array_sum($items));
}
}
return $items;
}
?>
| 維度 | 循環遞減法 | 區間切分法 |
|---|---|---|
| 時間複雜度 | O(N) | O(M) 其中 M = 總金額(分),大額紅包極慢 |
| 記憶體 | O(1) | O(M) 需建立巨大陣列 |
| 隨機分布 | 前期偏大,後期極小 | 理論上均勻分布於所有合法組合 |
| 浮點誤差 | 需注意 number_format | 整數運算天然避免 |
| 適用場景 | 金額小、紅包少、快速原型 | 教學展示、極小金額(如 1 元以內) |
<?php
function generateRedPackets(int $totalCents, int $count, int $minCents = 1): array {
// 參數檢查
if ($totalCents < $count * $minCents) throw new \InvalidArgumentException('總金額不足');
$packets = [];
$remaining = $totalCents;
for ($i = 1; $i < $count; $i++) {
// 動態上限:(餘額 - 剩餘最小保障) / 剩餘人數
$max = ($remaining - ($count - $i) * $minCents) / ($count - $i);
$max = min($max, $remaining - ($count - $i) * $minCents); // 安全邊界
$val = mt_rand($minCents, (int)$max);
$packets[] = $val;
$remaining -= $val;
}
$packets[] = $remaining; // 最後一包
shuffle($packets); // 打亂順序,消除順序偏差
return $packets;
}
// 使用:200 分,8 包,最小 1 分
$packets = generateRedPackets(200, 8);
echo "總和:" . array_sum($packets) . " 分\n";
print_r($packets);
?>
(剩餘 - 剩餘人數×最小) / 剩餘人數,使分布集中於平均值。更多 PHP 高併發紅包架構、Redis Lua 原子腳本、常態分布紅包演算法、金融級精度運算,請參考本站專題系列。