微信紅包「手氣最佳」背後的算法邏輯是什麼?本文實現一個生產可用級的紅包拆分算法:隨機權重分配、保底 0.01 元、總額精確到分、結果隨機打亂,附完整 Java 代碼與核心邏輯解析,可直接用於項目。
public class RedPacketAlgorithm {
/**
* 拆分紅包
* @param totalAmount 總金額(元),如 10.00
* @param count 人數
* @return 每人金額列表(元),已打亂順序
*/
public static List<BigDecimal> split(BigDecimal totalAmount, int count) {
// 1. 參數校驗
if (totalAmount == null || totalAmount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("總金額必須 > 0");
}
if (count <= 0) throw new IllegalArgumentException("人數必須 > 0");
// 轉為分(整數運算避免浮點誤差)
long totalCents = totalAmount.multiply(BigDecimal.valueOf(100)).longValue();
long minCents = count; // 每人至少 1 分
if (totalCents < minCents) {
throw new IllegalArgumentException("總金額不足發最小值:" + count + "分");
}
// 2. 生成隨機權重
Random random = new Random();
double[] weights = new double[count];
double sumWeight = 0;
for (int i = 0; i < count; i++) {
// 權重範圍 [1, count*99] 避免全 0,可調整分佈形狀
double w = random.nextInt(count * 99) + 1;
weights[i] = w;
sumWeight += w;
}
// 3. 按比例分配,保底 1 分
List<BigDecimal> result = new ArrayList<>(count);
long allocated = 0;
for (int i = 0; i < count; i++) {
long cents;
if (i == count - 1) {
// 最後一人拿剩餘所有(含尾差修正)
cents = totalCents - allocated;
} else {
// 比例分配
double ratio = weights[i] / sumWeight;
cents = Math.max(1, (long) Math.floor(ratio * totalCents));
// 防止剩餘不足後續人最小值
long remainingPeople = count - i - 1;
long maxCanTake = totalCents - allocated - remainingPeople;
if (cents > maxCanTake) cents = maxCanTake;
}
allocated += cents;
result.add(BigDecimal.valueOf(cents).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP));
}
// 4. 打亂順序
Collections.shuffle(result);
return result;
}
}
| 環節 | 處理邏輯 | 原因 |
|---|---|---|
| 金額單位 | 統一用 分 計算,最終除以 100 轉元 | 避免 double 浮點誤差(0.1+0.2≠0.3) |
| 數據類型 | 運算用 long,結果用 BigDecimal | long 精確整數運算,BigDecimal 精確小數輸出 |
| 最小值保障 | Math.max(1, ...) + 剩餘額度檢查 | 確保每人 ≥ 1 分,且不擠佔後續人份額 |
| 尾差修正 | 最後一人直接拿 total - allocated | 消除除法取整/保底導致的總和偏差 |
| 隨機打亂 | Collections.shuffle(list) | 防止按搶奪順序推測金額分佈 |