You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

已知月度计划值的小时级流量平滑过渡算法Java实现求助

月度流量平滑过渡的Java实现方案

核心逻辑

要满足你的需求,核心是分非平滑时段和平滑过渡时段计算小时速率,同时严格保证月度总流量精准匹配计划值:

  • 非平滑时段:小时速率 = 月度计划值 ÷ 当月总小时数(需考虑大小月、闰年)
  • 平滑过渡时段分两种场景:
    1. 次月(t+1)有计划值:在次月的前N小时(N为用户配置的平滑间隔),从当月(t)的基础速率线性过渡到次月的基础速率,剩余时段用次月基础速率
    2. 次月计划值为0:在当月的最后N小时,从当月基础速率线性降至0,剩余时段用当月基础速率
  • 所有计算需做精度校验,确保月度总流量与计划值完全一致,避免浮点误差

具体实现步骤

  1. 实现工具方法计算指定月份的总小时数
  2. 计算月度基础小时速率(非平滑时段使用)
  3. 根据次月计划值的情况,计算平滑过渡时段的每小时速率
  4. 校验月度总流量,若有误差则微调最后几个时段的速率(或用高精度运算避免误差)

Java代码示例

import java.math.BigDecimal;
import java.time.YearMonth;
import java.util.ArrayList;
import java.util.List;

public class TrafficRateCalculator {

    // 获取指定年月的总小时数
    private int getTotalHoursOfMonth(int year, int month) {
        YearMonth yearMonth = YearMonth.of(year, month);
        return yearMonth.lengthOfMonth() * 24;
    }

    // 计算非平滑时段的基础小时速率
    private BigDecimal getBaseRate(BigDecimal monthlyPlan, int totalHours) {
        if (totalHours == 0) return BigDecimal.ZERO;
        return monthlyPlan.divide(BigDecimal.valueOf(totalHours), 10, BigDecimal.ROUND_HALF_UP);
    }

    // 生成当月所有小时的速率列表(考虑次月计划值)
    public List<BigDecimal> generateHourlyRates(int currentYear, int currentMonth, 
                                                BigDecimal currentMonthPlan, BigDecimal nextMonthPlan, 
                                                int smoothIntervalHours) {
        List<BigDecimal> hourlyRates = new ArrayList<>();
        int totalCurrentHours = getTotalHoursOfMonth(currentYear, currentMonth);
        BigDecimal currentBaseRate = getBaseRate(currentMonthPlan, totalCurrentHours);

        if (nextMonthPlan.compareTo(BigDecimal.ZERO) == 0) {
            // 场景1:次月计划值为0,当月最后N小时平滑降为0
            int nonSmoothHours = totalCurrentHours - smoothIntervalHours;
            // 边界处理:若平滑间隔超过当月总小时数,取全部时段做平滑
            nonSmoothHours = Math.max(nonSmoothHours, 0);
            smoothIntervalHours = totalCurrentHours - nonSmoothHours;
            
            // 添加非平滑时段的速率
            for (int i = 0; i < nonSmoothHours; i++) {
                hourlyRates.add(currentBaseRate);
            }
            // 计算平滑时段的速率,保证总流量精准
            BigDecimal totalSmoothTraffic = currentMonthPlan.subtract(currentBaseRate.multiply(BigDecimal.valueOf(nonSmoothHours)));
            for (int i = 1; i <= smoothIntervalHours; i++) {
                BigDecimal ratio = BigDecimal.valueOf(smoothIntervalHours - i + 1).divide(BigDecimal.valueOf(smoothIntervalHours), 10, BigDecimal.ROUND_HALF_UP);
                BigDecimal rate = totalSmoothTraffic.multiply(ratio).divide(BigDecimal.valueOf(smoothIntervalHours - i + 1), 10, BigDecimal.ROUND_HALF_UP);
                hourlyRates.add(rate);
            }
        } else {
            // 场景2:次月有计划值,当月全时段用基础速率(平滑过渡在次月处理)
            for (int i = 0; i < totalCurrentHours; i++) {
                hourlyRates.add(currentBaseRate);
            }
        }

        // 校验总流量,修正精度误差
        BigDecimal total = hourlyRates.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
        if (total.compareTo(currentMonthPlan) != 0) {
            BigDecimal diff = currentMonthPlan.subtract(total);
            hourlyRates.set(hourlyRates.size() - 1, hourlyRates.get(hourlyRates.size() - 1).add(diff));
        }

        return hourlyRates;
    }

    // 生成次月的速率列表(处理从当月到次月的平滑过渡)
    public List<BigDecimal> generateNextMonthRates(int nextYear, int nextMonth, 
                                                  BigDecimal nextMonthPlan, BigDecimal prevBaseRate, 
                                                  int smoothIntervalHours) {
        List<BigDecimal> hourlyRates = new ArrayList<>();
        int totalNextHours = getTotalHoursOfMonth(nextYear, nextMonth);
        BigDecimal nextBaseRate = getBaseRate(nextMonthPlan, totalNextHours);

        int nonSmoothHours = totalNextHours - smoothIntervalHours;
        // 边界处理:若平滑间隔超过次月总小时数,取全部时段做平滑
        nonSmoothHours = Math.max(nonSmoothHours, 0);
        smoothIntervalHours = totalNextHours - nonSmoothHours;
        
        // 计算平滑时段的总流量,确保月度总流量精准
        BigDecimal nonSmoothTotal = nextBaseRate.multiply(BigDecimal.valueOf(nonSmoothHours));
        BigDecimal smoothTotal = nextMonthPlan.subtract(nonSmoothTotal);

        // 生成平滑过渡时段的速率,从旧速率线性过渡到新速率
        for (int i = 1; i <= smoothIntervalHours; i++) {
            BigDecimal ratio = BigDecimal.valueOf(i).divide(BigDecimal.valueOf(smoothIntervalHours), 10, BigDecimal.ROUND_HALF_UP);
            BigDecimal rate = prevBaseRate.multiply(BigDecimal.ONE.subtract(ratio)).add(nextBaseRate.multiply(ratio));
            // 调整速率以匹配平滑总流量,避免线性计算的误差
            BigDecimal linearTotal = prevBaseRate.multiply(BigDecimal.valueOf(smoothIntervalHours))
                    .add(nextBaseRate.subtract(prevBaseRate).multiply(BigDecimal.valueOf(smoothIntervalHours + 1)).divide(BigDecimal.valueOf(2), 10, BigDecimal.ROUND_HALF_UP));
            BigDecimal adjustedRate = rate.multiply(smoothTotal).divide(linearTotal, 10, BigDecimal.ROUND_HALF_UP);
            hourlyRates.add(adjustedRate);
        }

        // 添加非平滑时段的速率
        for (int i = 0; i < nonSmoothHours; i++) {
            hourlyRates.add(nextBaseRate);
        }

        // 校验并修正总流量误差
        BigDecimal total = hourlyRates.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
        if (total.compareTo(nextMonthPlan) != 0) {
            BigDecimal diff = nextMonthPlan.subtract(total);
            hourlyRates.set(hourlyRates.size() - 1, hourlyRates.get(hourlyRates.size() - 1).add(diff));
        }

        return hourlyRates;
    }

    public static void main(String[] args) {
        TrafficRateCalculator calculator = new TrafficRateCalculator();
        // 示例:当月计划1000,次月计划2000,平滑间隔24小时
        List<BigDecimal> currentRates = calculator.generateHourlyRates(2024, 5, new BigDecimal("1000"), new BigDecimal("2000"), 24);
        List<BigDecimal> nextRates = calculator.generateNextMonthRates(2024, 6, new BigDecimal("2000"), calculator.getBaseRate(new BigDecimal("1000"), 31*24), 24);
        
        // 输出验证
        System.out.println("当月前3小时速率:" + currentRates.subList(0,3));
        System.out.println("次月前3小时速率:" + nextRates.subList(0,3));
        System.out.println("次月最后3小时速率:" + nextRates.subList(nextRates.size()-3, nextRates.size()));
    }
}

关键注意事项

  • 精度控制:必须用BigDecimal替代double/float,避免浮点运算的精度丢失,尤其是涉及流量、金额等需要精准计算的场景
  • 边界处理:自动处理平滑间隔超过当月/次月总小时数的情况,避免数组越界或逻辑错误
  • 跨月扩展:如果需要跨月平滑(比如当月最后几小时到次月开头),可拆分平滑时段到两个月,分别计算速率并保证各自月度总流量精准
  • 性能优化:预先计算基础值和比例,避免重复运算,生成大量小时速率时可提升效率

内容的提问来源于stack exchange,提问作者Yura

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 20:55:23