首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 實現微信紅包算法:隨機分佈、最小值保障、總額精確控制完整代碼

     編輯:Java 相關知識

微信紅包「手氣最佳」背後的算法邏輯是什麼?本文實現一個生產可用級的紅包拆分算法:隨機權重分配、保底 0.01 元、總額精確到分、結果隨機打亂,附完整 Java 代碼與核心邏輯解析,可直接用於項目。

算法設計目標

  1. 隨機性:金額分佈不均勻,有大有小,增加趣味性。
  2. 公平性:期望值均等,人人有機會拿大額。
  3. 最小值保障:每人 ≥ 0.01 元(1 分)。
  4. 總額精確:分發總和嚴格等於紅包總額,無誤差。
  5. 結果無序:打亂順序,防止按搶奪順序推測金額。

核心邏輯:隨機權重 + 比例分配 + 尾差修正

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;
    }
}

關鍵細節解析

測試驗證

public static void main(String[] args) {
    for (int i = 0; i < 10; i++) {
        List<BigDecimal> list = split(BigDecimal.valueOf(10), 6); // 10元 6人
        BigDecimal sum = BigDecimal.ZERO;
        for (BigDecimal v : list) {
            System.out.print(v + "元 ");
            sum = sum.add(v);
        }
        System.out.println(" 總額:" + sum + "元");
    }
}
/* 輸出示例:
2.34元 0.89元 3.12元 0.56元 1.78元 1.31元  總額:10.00元
0.01元 4.56元 2.10元 0.78元 1.23元 1.32元  總額:10.00元
... */

生產環境擴展建議

  1. 高併發鎖:Redis Lua 腳本 / Redisson 分佈式鎖保證原子扣減庫存。
  2. 防作弊:限制同 IP/設備/用戶頻率,風控引擎識別腳本搶包。
  3. 記賬一致性:紅包發出 → 凍結金額 → 拆分完成 → 解凍/實際扣款,異步補償機制。
  4. 大額紅包優化:金額大、人數多時,可採用「二倍均值法」或「指數分佈」替代簡單權重,分佈更自然。
  5. 審計日誌:記錄紅包 ID、發起人、總額、人數、分發明細、時間戳,便於對賬追溯。

總結

紅包算法核心是隨機權重比例分配 + 整數分單位運算 + 尾差修正 + 打亂。上述代碼經過邊界測試(1元100人、100元2人、最小值邊界),滿足生產級精度與性能要求。接入項目時,記得包裝為 RedPacketService,配合 Redis 原子操作、風控、異步補償,即可支撐千萬級搶紅包場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
環節處理邏輯原因
金額單位統一用 分 計算,最終除以 100 轉元避免 double 浮點誤差(0.1+0.2≠0.3)
數據類型運算用 long,結果用 BigDecimallong 精確整數運算,BigDecimal 精確小數輸出
最小值保障Math.max(1, ...) + 剩餘額度檢查確保每人 ≥ 1 分,且不擠佔後續人份額
尾差修正最後一人直接拿 total - allocated消除除法取整/保底導致的總和偏差
隨機打亂Collections.shuffle(list)防止按搶奪順序推測金額分佈