JAVA:实现求Average平均数算法(附带源码)
JAVA:实现求Average平均数算法(附带源码)
项目背景详细介绍
平均数(Average)是统计学与数据分析中最基础、最常用的描述集中趋势的量度之一。不同形式的平均数(算术平均数、几何平均数、调和平均数、加权平均数、中位数、截尾平均、移动平均、指数加权移动平均等)在不同场景中有着明确而重要的意义:
-
算术平均数用于描述总和分布(例如班级的平均分、传感器读数的均值);
-
几何平均数用于处理比率或相乘增长的量(例如年化收益率、多期增长率的平均);
-
调和平均数用于平均速率或密度场景(例如平均速度、单位成本);
-
加权平均用于给不同样本赋予不同重要性(例如评分系统、推荐引擎);
-
中位数与截尾平均用于抗异常值(鲁棒统计);
-
移动平均用于信号平滑与时间序列分析,指数加权平均用于实时滤波与衰减历史影响;
-
在线平均(running average)与数值稳定版本(Welford 算法)在流数据或内存受限场景中尤为重要。
工程上实现“平均数”看似简单,但却有诸多实际问题需要注意:数值稳定性(大体量和/或大数相加导致溢出)、精度(double 累加误差)、内存(一次性加载全部数据不可行时需要在线算法)、并行化(MapReduce 风格的合并平均)、多类型支持(int/long/double/BigDecimal/BigInteger)、以及 API 需求(是否返回中间步骤、是否支持 Java Stream、是否带权重、是否支持滑动窗口与指数加权等)。此外,在教学上需要一套详尽的、可运行的示例代码,以便说明每种平均的数学原理、实现复杂度与适用场景。
项目需求详细介绍
-
功能需求
-
实现并公开以下平均数计算函数(至少):
-
算术平均(Arithmetic Mean):数组/列表/流输入,支持 int/long/double/BigDecimal。
-
几何平均(Geometric Mean):适用于非负数或正数序列,支持 double 与 BigDecimal。
-
调和平均(Harmonic Mean):适用于速率类数据,注意不能有 0。
-
加权平均(Weighted Mean):支持权重数组/集合,长度需一致,权重可为 double 或 BigDecimal。
-
截尾平均(Trimmed Mean):按百分比截尾两端异常值后求平均。
-
中位数(Median):虽然严格不是“平均”但常与平均一起比较,提供作为对比指标并用于计算中位数平均。
-
简单移动平均(SMA)和指数加权移动平均(EWMA / EMA):适用于时间序列,提供窗口大小或平滑因子。
-
在线(流式)平均:含 Welford 的数值稳定实现,用于单次遍历且可返回方差。
-
并行可合并统计(mergeable aggregator):能将不同分片的统计量合并得到总平均与方差,方便 MapReduce / 并行计算。
-
-
提供数值稳定选项(例如 Kahan summation)与高精度 BigDecimal 实现。
-
提供 Java Stream 示例(DoubleStream、LongStream)以展示声明式 API。
-
所有方法做参数校验并在非法参数时抛出
IllegalArgumentException(例如数组为空、长度不一致、权重和为0、负数传入几何平均等)。
-
-
代码组织
main包含示例用法:各种平均的典型输入、异常输入演示、数值稳定性对比(例如 Kahan vs 普通累加)与性能耗时(纳秒)。 -
教学要求
-
文档中在“实现思路”部分详细讲解数值误差、Kahan 和 Welford 算法的原理、截尾平均的统计学背景、几何/调和平均的数学适用性。
-
相关技术详细介绍
-

-
截尾平均(Trimmed Mean)
去掉最高和最低一定比例的数据后求平均,适合对抗异常值。截尾比例通常在 000 到 0.50.50.5 之间。 -
移动平均(SMA 与 EMA)
-
SMA:滑动窗口内算术平均;时间复杂度 O(n)(可用滑动累加到 O(1) 每步)。
-
EMA(指数加权):递归
S_t = α x_t + (1-α) S_{t-1},权重随指数衰减,适合实时滤波。
-
-
在线 / 数值稳定算法
-
Welford 算法用于一次遍历同时求均值和方差,数值稳定且可合并:维护
n,mean,M2,合并两组统计也有公式。 -
Kahan summation 用于减少浮点累加误差。
-
-
并行可合并聚合
在分布式或并行场景中,每个分片计算(count, mean, M2),最终聚合能得到全局均值与方差,避免传输原始数据。 -
精度与性能权衡
-
BigDecimal提供高精度但性能慢;double快但有舍入误差;Welford + Kahan 提供在double下很好的稳定性。选择取决于是否需要精确数值还是性能优先。
-
实现思路详细介绍
-
API 设计:创建
AverageUtils工具类,提供静态方法集合,并为不同数据类型(int[],long[],double[],BigDecimal[])分别实现重载。还实现OnlineStats类实现 Welford 的在线均值与方差函数,并提供merge方法以支持并行合并。 -
数值稳定实现:
-
算术平均:默认实现用 Kahan summation(减少舍入误差),同时提供普通实现以便性能对比。
-
在线与并行场景:实现 Welford 算法:在一次遍历中更新
count, mean, M2,最后方差为M2/(count - 1)(样本方差)或M2/count(总体方差)。Welford 算法数值稳定并可合并。 -
几何平均:使用对数求和
sum(log(x))然后Math.exp(sum / n);对小数或大数用BigDecimal或多重精度库。
-
-
高精度 BigDecimal 实现:实现
BigDecimal版本的算术平均(使用指定MathContext),以及 BigDecimal 的加权平均、几何平均(若需用BigDecimal的ln/exp实现,Java 标准库未提供;此处给出对数近似说明并提供对 BigDecimal 的简单近似实现或建议使用BigDecimal做乘积并开 nth 根,注意性能)。在代码中对 BigDecimal 的 ln/exp 未做复杂实现,而是建议对需要高精度几何平均的情形使用BigDecimal做对数近似或外部库。 -
截尾平均实现:对数据进行排序(或利用选择算法找到阈值)并丢弃两端
k个值后求平均;时间复杂度 O(nlogn)O(n \log n)O(nlogn)(排序)或 O(n)O(n)O(n)(选择算法)。 -
移动平均实现:SMA 采用滑动窗口与队列或循环数组实现
O(1)每步更新;EMA 提供递归公式,参数alpha在 0~1。 -
并行与 Stream:提供
DoubleStream示例计算平均值(内置average()),并演示如何使用collect与OnlineStats来得到在线累加并行安全的聚合器。 -
异常与校验:对所有方法检查
null、长度 0、特殊值(例如几何平均出现负值)、权重和为 0 等,抛出IllegalArgumentException并指明原因。 -
示例与对比:
main中提供各种输入数据(含异常与极值),比较普通累加、Kahan、Welford 与 BigDecimal 结果,打印耗时与误差,解释差异与选择理由。
完整实现代码
/* ============================================================
文件: AverageProject.java
说明: 平均数(Average)计算工具合集(教学 + 工程)
- Arithmetic Mean(普通/Kahan/BigDecimal)
- Geometric Mean(对数法 & BigDecimal 注意事项)
- Harmonic Mean
- Weighted Mean(double / BigDecimal)
- Trimmed Mean(截尾平均)
- Median(中位数 - 用作对比)
- Simple Moving Average (SMA) & Exponential Moving Average (EMA)
- OnlineStats (Welford) 可在线与可合并统计 mean/variance
- Java Stream 示例(DoubleStream / LongStream)
编译: javac AverageProject.java
运行: java AverageProject
============================================================ */
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.DoubleStream;
import java.util.stream.LongStream;
public class AverageProject {
// ============================
// 文件: AverageUtils.java
// 各类平均数实现(静态工具方法)
// ============================
public static class AverageUtils {
/* ====================================================
方法: arithmeticMean(double[] arr)
作用: 计算 double 数组的算术平均数(使用 Kahan summation 减小累加误差)
参数: arr - 非空 double 数组
返回: double 平均值
时间复杂度: O(n)
空间复杂度: O(1)
注意: 若需要更高精度可使用 BigDecimal 版本
==================================================== */
public static double arithmeticMean(double[] arr) {
if (arr == null) throw new IllegalArgumentException("输入数组不能为 null");
int n = arr.length;
if (n == 0) throw new IllegalArgumentException("数组长度必须大于 0");
// Kahan summation
double sum = 0.0;
double c = 0.0; // 补偿项
for (double v : arr) {
double y = v - c;
double t = sum + y;
c = (t - sum) - y;
sum = t;
}
return sum / n;
}
/* ====================================================
方法: arithmeticMeanPlain(double[] arr)
作用: 直接使用普通累加计算平均(快速但误差较大)
参数: arr - 非空 double 数组
返回: double
时间复杂度: O(n)
注意: 仅用于对比或在数值范围较小时使用
==================================================== */
public static double arithmeticMeanPlain(double[] arr) {
if (arr == null) throw new IllegalArgumentException("输入数组不能为 null");
int n = arr.length;
if (n == 0) throw new IllegalArgumentException("数组长度必须大于 0");
double sum = 0.0;
for (double v : arr) sum += v;
return sum / n;
}
/* ====================================================
方法: arithmeticMeanLong(long[] arr)
作用: 计算 long 数组的算术平均,使用双精度累加避免溢出并返回 double
参数: arr - 非空 long 数组
返回: double
复杂度: O(n)
注意: 若需精确整数平均(分子/分母),请使用 BigDecimal 或 分数表示
==================================================== */
public static double arithmeticMeanLong(long[] arr) {
if (arr == null) throw new IllegalArgumentException("输入数组不能为 null");
int n = arr.length;
if (n == 0) throw new IllegalArgumentException("数组长度必须大于 0");
double sum = 0.0;
double c = 0.0;
for (long v : arr) {
double y = v - c;
double t = sum + y;
c = (t - sum) - y;
sum = t;
}
return sum / n;
}
/* ====================================================
方法: arithmeticMeanBigDecimal(BigDecimal[] arr, MathContext mc)
作用: 使用 BigDecimal 在指定 MathContext 下计算算术平均,精度可控
参数: arr - 非空 BigDecimal 数组,mc - MathContext 精度与舍入模式
返回: BigDecimal 平均值
复杂度: O(n * cost(BigDecimal ops))
注意: BigDecimal 运算昂贵,仅在需要高精度时使用
==================================================== */
public static BigDecimal arithmeticMeanBigDecimal(BigDecimal[] arr, MathContext mc) {
if (arr == null) throw new IllegalArgumentException("输入数组不能为 null");
if (mc == null) throw new IllegalArgumentException("MathContext 不能为 null");
int n = arr.length;
if (n == 0) throw new IllegalArgumentException("数组长度必须大于 0");
BigDecimal sum = BigDecimal.ZERO;
for (BigDecimal v : arr) {
if (v == null) throw new IllegalArgumentException("数组元素不能为 null");
sum = sum.add(v, mc);
}
return sum.divide(BigDecimal.valueOf(n), mc);
}
/* ====================================================
方法: geometricMean(double[] arr)
作用: 计算几何平均:exp( (1/n) * sum(log(x_i)) )(要求所有 x_i > 0)
参数: arr - 非空 double 数组,元素必须 > 0
返回: double
复杂度: O(n)
注意: 对可能少于或等于 0 的元素抛出异常;使用 log/exp 可避免直接乘积溢出
==================================================== */
public static double geometricMean(double[] arr) {
if (arr == null) throw new IllegalArgumentException("输入数组不能为 null");
int n = arr.length;
if (n == 0) throw new IllegalArgumentException("数组长度必须大于 0");
double sumLog = 0.0;
for (double v : arr) {
if (v <= 0.0) throw new IllegalArgumentException("几何平均要求所有元素 > 0");
sumLog += Math.log(v);
}
return Math.exp(sumLog / n);
}
/* ====================================================
方法: harmonicMean(double[] arr)
作用: 计算调和平均:n / sum(1/x_i)(所有 x_i != 0)
参数: arr - 非空 double 数组,元素不能为 0
返回: double
复杂度: O(n)
注意: 若存在 0 元素,调和平均未定义(或视为 0),抛出异常
==================================================== */
public static double harmonicMean(double[] arr) {
if (arr == null) throw new IllegalArgumentException("输入数组不能为 null");
int n = arr.length;
if (n == 0) throw new IllegalArgumentException("数组长度必须大于 0");
double sumRecip = 0.0;
for (double v : arr) {
if (v == 0.0) throw new IllegalArgumentException("调和平均要求所有元素非 0");
sumRecip += 1.0 / v;
}
return n / sumRecip;
}
/* ====================================================
方法: weightedMean(double[] values, double[] weights)
作用: 计算加权平均:sum(w_i * x_i) / sum(w_i)
参数: values, weights - 非空数组且长度一致,权重和不能为 0
返回: double
复杂度: O(n)
注意: 权重可为负但需谨慎解释
==================================================== */
public static double weightedMean(double[] values, double[] weights) {
if (values == null || weights == null) throw new IllegalArgumentException("输入不能为 null");
int n = values.length;
if (n == 0 || weights.length != n) throw new IllegalArgumentException("长度必须一致且大于 0");
double sumW = 0.0;
double sumWX = 0.0;
double cW = 0.0, cWX = 0.0; // Kahan 补偿分别用于权重和与加权和
for (int i = 0; i < n; i++) {
double w = weights[i];
double x = values[i];
// sumW += w (Kahan)
double yW = w - cW;
double tW = sumW + yW;
cW = (tW - sumW) - yW;
sumW = tW;
// sumWX += w * x (Kahan)
double wx = w * x;
double yWX = wx - cWX;
double tWX = sumWX + yWX;
cWX = (tWX - sumWX) - yWX;
sumWX = tWX;
}
if (Math.abs(sumW) < 1e-300) throw new IllegalArgumentException("权重和接近零 (可能导致除以零)");
return sumWX / sumW;
}
/* ====================================================
方法: weightedMeanBigDecimal(BigDecimal[] values, BigDecimal[] weights, MathContext mc)
作用: BigDecimal 版本的加权平均,精度由 MathContext 控制
参数: 非空数组、长度一致、mc 非空
返回: BigDecimal
复杂度: O(n * cost(BigDecimal ops))
==================================================== */
public static BigDecimal weightedMeanBigDecimal(BigDecimal[] values, BigDecimal[] weights, MathContext mc) {
if (values == null || weights == null) throw new IllegalArgumentException("输入不能为 null");
if (mc == null) throw new IllegalArgumentException("MathContext 不能为 null");
int n = values.length;
if (n == 0 || weights.length != n) throw new IllegalArgumentException("长度必须一致且大于 0");
BigDecimal sumW = BigDecimal.ZERO;
BigDecimal sumWX = BigDecimal.ZERO;
for (int i = 0; i < n; i++) {
if (values[i] == null || weights[i] == null) throw new IllegalArgumentException("数组元素不能为 null");
sumW = sumW.add(weights[i], mc);
sumWX = sumWX.add(weights[i].multiply(values[i], mc), mc);
}
if (sumW.compareTo(BigDecimal.ZERO) == 0) throw new IllegalArgumentException("权重和不能为 0");
return sumWX.divide(sumW, mc);
}
/* ====================================================
方法: trimmedMean(double[] arr, double trimFraction)
作用: 计算截尾平均(去掉两端 trimFraction 的数据后求平均)
参数: arr 非空数组,trimFraction in [0, 0.5)
返回: double
复杂度: O(n log n)(排序)或可用选择算法降至 O(n)
注意: trimFraction 为比例,例如 0.1 表示去掉两端各 10%
==================================================== */
public static double trimmedMean(double[] arr, double trimFraction) {
if (arr == null) throw new IllegalArgumentException("输入数组不能为 null");
int n = arr.length;
if (n == 0) throw new IllegalArgumentException("数组长度必须大于 0");
if (trimFraction < 0.0 || trimFraction >= 0.5) throw new IllegalArgumentException("trimFraction 必须在 [0, 0.5)");
double[] copy = Arrays.copyOf(arr, n);
Arrays.sort(copy);
int k = (int) Math.floor(trimFraction * n);
int start = k;
int end = n - k; // exclusive
if (start >= end) throw new IllegalArgumentException("trimFraction 太大导致无数据");
double sum = 0.0;
for (int i = start; i < end; i++) sum += copy[i];
return sum / (end - start);
}
/* ====================================================
方法: median(double[] arr)
作用: 计算中位数(用于对比)
参数: arr 非空数组
返回: double 中位数
复杂度: O(n log n)(排序)或 O(n) 使用选择算法
==================================================== */
public static double median(double[] arr) {
if (arr == null) throw new IllegalArgumentException("输入数组不能为 null");
int n = arr.length;
if (n == 0) throw new IllegalArgumentException("数组长度必须大于 0");
double[] copy = Arrays.copyOf(arr, n);
Arrays.sort(copy);
if ((n & 1) == 1) return copy[n / 2];
else return (copy[n / 2 - 1] + copy[n / 2]) / 2.0;
}
/* ====================================================
方法: simpleMovingAverage(double[] arr, int window)
作用: 计算简单移动平均(SMA),返回与输入相同长度的数组(前 window-1 个位置用 Double.NaN 表示)
参数: arr 非空数组,window >= 1
返回: double[] SMA
复杂度: O(n)
==================================================== */
public static double[] simpleMovingAverage(double[] arr, int window) {
if (arr == null) throw new IllegalArgumentException("输入数组不能为 null");
int n = arr.length;
if (window <= 0) throw new IllegalArgumentException("window 必须 >= 1");
if (n == 0) return new double[0];
double[] res = new double[n];
double sum = 0.0;
for (int i = 0; i < n; i++) {
sum += arr[i];
if (i >= window) sum -= arr[i - window];
if (i >= window - 1) res[i] = sum / window;
else res[i] = Double.NaN;
}
return res;
}
/* ====================================================
方法: exponentialMovingAverage(double[] arr, double alpha)
作用: 计算指数加权移动平均(EMA),S_t = alpha*x_t + (1-alpha)*S_{t-1}
参数: arr 非空数组,alpha in (0,1]
返回: double[] EMA,与输入长度相同(初始 S_0 取 arr[0])
复杂度: O(n)
==================================================== */
public static double[] exponentialMovingAverage(double[] arr, double alpha) {
if (arr == null) throw new IllegalArgumentException("输入数组不能为 null");
if (alpha <= 0.0 || alpha > 1.0) throw new IllegalArgumentException("alpha 必须在 (0,1]");
int n = arr.length;
if (n == 0) return new double[0];
double[] res = new double[n];
double s = arr[0];
res[0] = s;
for (int i = 1; i < n; i++) {
s = alpha * arr[i] + (1 - alpha) * s;
res[i] = s;
}
return res;
}
/* ====================================================
方法: arithmeticMeanStream(DoubleStream stream)
作用: 使用 Java DoubleStream 计算平均(返回 OptionalDouble)
参数: stream 非空(注意 stream 只可消费一次)
返回: OptionalDouble(当流为空时为空)
复杂度: O(n)
==================================================== */
public static OptionalDouble arithmeticMeanStream(DoubleStream stream) {
if (stream == null) throw new IllegalArgumentException("stream 不能为 null");
return stream.average();
}
/* ====================================================
方法: arithmeticMeanLongStream(LongStream stream)
作用: 使用 LongStream 计算平均(返回 OptionalDouble)
==================================================== */
public static OptionalDouble arithmeticMeanLongStream(LongStream stream) {
if (stream == null) throw new IllegalArgumentException("stream 不能为 null");
return stream.average();
}
} // end AverageUtils
// ============================
// 文件: OnlineStats.java
// 说明: Welford 在线算法,支持更新与合并(可并行合并)
// 维护三个量:count, mean, M2(用于计算方差)
// ============================
public static class OnlineStats {
private long count;
private double mean;
private double m2; // sum of squares of differences from the mean (用于方差)
public OnlineStats() {
this.count = 0L;
this.mean = 0.0;
this.m2 = 0.0;
}
/**
* 增加一个观测值 x,并更新 count, mean, m2
* 时间复杂度: O(1)
*/
public void add(double x) {
count++;
double delta = x - mean;
mean += delta / count;
double delta2 = x - mean;
m2 += delta * delta2;
}
/**
* 合并另一个 OnlineStats 到当前对象(并行可合并)
* 时间复杂度: O(1)
* 公式参考 Welford 的可合并形式
*/
public void merge(OnlineStats other) {
if (other == null) return;
if (other.count == 0) return;
if (this.count == 0) {
this.count = other.count;
this.mean = other.mean;
this.m2 = other.m2;
return;
}
long combinedCount = this.count + other.count;
double delta = other.mean - this.mean;
this.mean = (this.mean * this.count + other.mean * other.count) / combinedCount;
this.m2 = this.m2 + other.m2 + delta * delta * ( (double)this.count * other.count / combinedCount );
this.count = combinedCount;
}
public long getCount() { return count; }
public double getMean() { return mean; }
public double getPopulationVariance() { // sigma^2
if (count == 0) return Double.NaN;
return m2 / count;
}
public double getSampleVariance() { // s^2
if (count < 2) return Double.NaN;
return m2 / (count - 1);
}
public double getPopulationStdDev() { return Math.sqrt(getPopulationVariance()); }
public double getSampleStdDev() { return Math.sqrt(getSampleVariance()); }
} // end OnlineStats
// ============================
// 文件: StreamExamples.java
// 展示如何用 Java Stream 与并行环境计算平均(用 OnlineStats 做 collect)
// ============================
public static class StreamExamples {
/**
* 使用 DoubleStream 和 OnlineStats 做并行收集器示例(伪代码式)
* 这里直接展示如何借助 stream.reduce/collect 实现并行安全的平均与方差计算
*/
public static OnlineStats computeStatsFromDoubleStream(DoubleStream stream) {
if (stream == null) throw new IllegalArgumentException("stream 不能为 null");
// 注意: 下面示例如果使用 stream.parallel() 则需要使用适合并行的 collector,这里演示思路:
OnlineStats stats = stream.collect(
OnlineStats::new, // supplier
(s, v) -> s.add(v), // accumulator
(s1, s2) -> s1.merge(s2) // combiner
);
return stats;
}
}
// ============================
// 文件: DemoMain.java
// main() 演示各种平均的使用与对比(包含耗时与数值差异)
// ============================
public static void main(String[] args) {
System.out.println("=== AverageProject Demo ===");
// 示例数据
double[] data = {1.0, 2.0, 3.0, 4.0, 5.0, 1e18, -1e18}; // 包含大数测试累加误差
System.out.println("\n数据: " + Arrays.toString(data));
// 算术平均:Kahan vs plain
long t0 = System.nanoTime();
double meanKahan = AverageUtils.arithmeticMean(data);
long t1 = System.nanoTime();
double meanPlain = AverageUtils.arithmeticMeanPlain(data);
long t2 = System.nanoTime();
System.out.printf("arithmeticMean (Kahan) = %.12f (ns=%d)%n", meanKahan, (t1 - t0));
System.out.printf("arithmeticMeanPlain = %.12f (ns=%d)%n", meanPlain, (t2 - t1));
// Welford 在线统计
OnlineStats os = new OnlineStats();
for (double v : data) os.add(v);
System.out.printf("Online mean = %.12f, sample variance = %.6e%n", os.getMean(), os.getSampleVariance());
// 加权平均
double[] vals = {1.0, 2.0, 3.0};
double[] wts = {0.1, 0.1, 0.8};
System.out.printf("weightedMean = %.6f%n", AverageUtils.weightedMean(vals, wts));
// 几何平均(正数)
double[] pos = {1.0, 2.0, 3.0, 4.0};
System.out.printf("geometricMean = %.6f%n", AverageUtils.geometricMean(pos));
// 调和平均
double[] speeds = {60.0, 40.0}; // 两段路程速度示例
System.out.printf("harmonicMean = %.6f%n", AverageUtils.harmonicMean(speeds));
// 中位数与截尾平均
double[] skew = {1,2,3,1000,10000};
System.out.printf("median = %.6f, trimmedMean(20%%) = %.6f%n",
AverageUtils.median(skew), AverageUtils.trimmedMean(skew, 0.2));
// SMA 与 EMA
double[] series = {1,2,3,4,5,6,7,8,9,10};
System.out.println("SMA (window 3): " + Arrays.toString(AverageUtils.simpleMovingAverage(series, 3)));
System.out.println("EMA (alpha=0.3): " + Arrays.toString(AverageUtils.exponentialMovingAverage(series, 0.3)));
// BigDecimal 平均示例
BigDecimal[] big = { new BigDecimal("12345678901234567890"), new BigDecimal("98765432109876543210") };
MathContext mc = new MathContext(20, RoundingMode.HALF_UP);
System.out.println("BigDecimal arithmetic mean = " + AverageUtils.arithmeticMeanBigDecimal(big, mc).toPlainString());
// Stream 示例:DoubleStream
DoubleStream ds = DoubleStream.of(series);
OptionalDouble od = AverageUtils.arithmeticMeanStream(ds);
System.out.println("DoubleStream average = " + (od.isPresent() ? od.getAsDouble() : "empty"));
// 并行统计示例(OnlineStats via Stream collector)
double[] large = new double[1000000];
Random rnd = new Random(123456);
for (int i = 0; i < large.length; i++) large[i] = rnd.nextDouble();
long t3 = System.nanoTime();
OnlineStats stats = StreamExamples.computeStatsFromDoubleStream(Arrays.stream(large));
long t4 = System.nanoTime();
System.out.printf("Large sample mean = %.12f (ns=%d)%n", stats.getMean(), (t4 - t3));
System.out.println("\n=== Demo End ===");
}
}
代码详细解读
-
AverageUtils.arithmeticMean(double[] arr):使用 Kahan summation 计算双精度数组的算术平均,减少累加误差,适合常规数值场景。 -
AverageUtils.arithmeticMeanPlain(double[] arr):直接普通累加实现的算术平均,性能略优但数值误差更大,常用于数据范围受限时。 -
AverageUtils.arithmeticMeanLong(long[] arr):把 long 值转为 double 使用 Kahan 累加计算平均并返回 double,避免 long 直接累加溢出。 -
AverageUtils.arithmeticMeanBigDecimal(BigDecimal[] arr, MathContext mc):使用 BigDecimal 在指定精度下计算平均,适合对精度非常敏感的场景(金融、会计)。 -
AverageUtils.geometricMean(double[] arr):对数法实现的几何平均,首先对元素取log累加再exp(sum/n),适用于正数比率与增长率场景。 -
AverageUtils.harmonicMean(double[] arr):计算调和平均n / sum(1/x_i),适用于速率类问题,拒绝含 0 的元素。 -
AverageUtils.weightedMean(double[] values, double[] weights):使用 Kahan 补偿的加权平均,计算sum(w_i * x_i) / sum(w_i),并校验权重和非零。 -
AverageUtils.weightedMeanBigDecimal(BigDecimal[] values, BigDecimal[] weights, MathContext mc):BigDecimal 版本的加权平均。 -
AverageUtils.trimmedMean(double[] arr, double trimFraction):截尾平均,实现为先排序再去掉两端比例数据后求平均,trimFraction ∈ [0, 0.5)。 -
AverageUtils.median(double[] arr):返回数组的中位数(排序实现)。 -
AverageUtils.simpleMovingAverage(double[] arr, int window):计算窗口大小为 window 的简单移动平均,返回同长度数组,前 window-1 项为 NaN。 -
AverageUtils.exponentialMovingAverage(double[] arr, double alpha):指数加权移动平均 (EMA),递归实现,alpha ∈ (0,1]。 -
AverageUtils.arithmeticMeanStream(DoubleStream stream)与ArithmeticMeanLongStream(LongStream stream):利用 Java Stream 的average()计算平均,返回 OptionalDouble。 -
OnlineStats类:实现 Welford 算法,提供add(double)更新、merge(OnlineStats other)合并、以及获取count, mean, population/sample variance/stddev的方法,适合在线计算与并行合并。 -
StreamExamples.computeStatsFromDoubleStream(DoubleStream stream):展示如何用 Stream 的collect将数据收集到OnlineStats(可并行)以获得稳定的均值与方差。
项目详细总结
-
功能覆盖:本文实现了常见与高级平均数算法的全套工具:算术平均(Kahan & plain)、几何/调和平均、加权平均、截尾平均、中位数、SMA/EMA、Welford 在线统计与并行合并、以及 BigDecimal 的高精度实现,满足教学与工程需求。
-
数值稳定性:采用了 Kahan summation 来减少双精度的累加误差,采用 Welford 算法来进行在线稳定均值与方差计算,并提供 BigDecimal 版本以满足高精度要求。
-
性能与规模:在大规模数据或流式环境推荐使用在线算法(Welford)与可合并统计,在并行/分布式场景下可以安全合并分片统计避免传输全部原始数据。
-
API 易用性:方法均做严格参数校验并抛出有意义的异常,方便上层调用者快速定位错误。
项目常见问题及解答
Q1:为什么 Kahan summation 比普通累加更准确?
A1:Kahan 使用一个补偿项来记录被丢失的小量,从而在后续累加中补偿,这能显著减小舍入误差,尤其在把大量小数加到大数上时。
Q2:什么时候使用 BigDecimal?
A2:当需要严格的十进制精度(例如财务计算、会计、法律合规场景)或 double 精度无法满足时,使用 BigDecimal。其代价是计算速度明显变慢与内存开销增大。
Q3:几何平均为什么要对数变换?
A3:直接把所有数相乘会在多项式或指数增长下快速溢出或下溢。对数变换把乘法转为加法,避免中间溢出,同时数值更稳定。
Q4:Welford 算法为什么可合并?
A4:Welford 维护 mean 与 M2(sum of squared deviations),有封闭的合并公式可以把两组统计合并为一组统计,从而适配并行或 MapReduce 场景。
Q5:截尾平均与中位数哪个对异常值更稳健?
A5:两者都比算术平均对异常值更稳健。中位数对极端异常最稳健(不受数值大小影响),截尾平均在保留更多信息的前提下兼顾稳健性和效率,常用于稳健统计分析。
扩展方向与性能优化建议
-
并行化与批处理:在多核或分布式环境中使用
OnlineStats的merge接口作为聚合器进行分片并行处理,然后合并结果,避免数据移动与重复计算。 -
无对象原生数组实现:为极致性能,将所有 BigDecimal/Complex(如果有)等对象化实现改为原始
double[]操作以减少 GC 开销。 -
选择算法替代排序:对于截尾平均或中位数,当数据量非常大时可使用选择算法(Quickselect)在平均线性时间内找到第 k 个元素以避免完整排序。
-
高精度对数/指数支持:在需要 BigDecimal 精度的几何平均场景下,建议集成成熟的多精度对数/指数库(例如 Apfloat、Big-Math 等)以准确实现
ln/exp。 -
滑动窗口高效实现:对滑动窗口统计,可以用双端队列或环形缓冲 + 分块技术(例如 DEMA / T-Digest)来提高效率或处理分位数等。
-
鲁棒性检测:在工程中对输入分布做预检查(是否存在 Inf/NaN/极值)并在文档中明确边界条件和异常处理策略,以免下游误用。
为武汉地区的开发者提供学习、交流和合作的平台。社区聚集了众多技术爱好者和专业人士,涵盖了多个领域,包括人工智能、大数据、云计算、区块链等。社区定期举办技术分享、培训和活动,为开发者提供更多的学习和交流机会。
更多推荐



所有评论(0)