commons-math3(科学计算与线性代数)
-
- 5、数值分析与插值 (Analysis)
-
- 5.1、核心 API 与分类体系
- 5.2、使用示例
-
- 5.2.1、数据插值(Interpolation)
- 5.2.2、一元函数求根(Root-Finding / Equation Solving)
- 5.2.3、数值积分(Numerical Integration)
- 5.3、避坑指南与最佳实践
- 6、优化与拟合 (Optim)
-
- 6.1、核心 API 与模块分类
- 6.2、使用示例
-
- 6.2.1、一元函数极值优化 (BrentOptimizer)
- 6.2.2、非线性多项式曲线拟合 (PolynomialCurveFitter)
- 6.2.3、自定义函数非线性曲线拟合 (AbstractCurveFitter / ParametricUnivariateFunction)
- 6.2.4、线性规划求解 (SimplexSolver)
- 6.3、避坑指南与最佳实践
- 7、分布与概率 (Distribution)
-
- 7.1、核心 API 体系
- 7.2、使用示例
-
- 7.2.1、连续型分布示例:正态分布 (NormalDistribution)
- 7.2.2、离散型分布示例:泊松分布 (PoissonDistribution)
- 7.2.3、离散型分布示例:二项分布 (BinomialDistribution)
- 7.3、高级用法:自定义伪随机种子
- 7.4、避坑指南与最佳实践
- 8、复数与数值计算 (Complex/Util)
-
- 8.1、核心 API 体系
- 8.2、使用示例
-
- 8.2.1、复数基础与高级运算 (Complex)
- 8.2.2、快速傅里叶变换 (FastFourierTransformer)
- 8.2.3、安全算术与组合数 (ArithmeticUtils)
- 8.2.4、高精度浮点数安全比较 (Precision)
- 8.2.5、高性能数学计算 (FastMath)
- 8.3、避坑指南与最佳实践
5、数值分析与插值 (Analysis)
在 commons-math3 中,线性代数(Linear Algebra) 模块提供了极具实用价值且数值稳定的 API。该模块的核心集中在 org.apache.commons.math3.linear 包下,覆盖了矩阵/向量操作、线性方程组求解、矩阵分解(LU、QR、Cholesky、SVD) 等标准代数运算。
5.1、核心 API 与分类体系
| 插值算法 (Interpolation) | UnivariateInterpolatorSplineInterpolatorPolynomialSplineFunction | 给定离散点,拟合平滑曲线。包括:三次样条插值(Spline)、拉格朗日插值、分段赫米特插值(Pchip) |
| 函数求根 (Root-Finding) | UnivariateSolverBrentSolverNewtonRaphsonSolver | 求解一元方程
f ( x ) = 0 f(x) = 0 f(x)=0 的根。包括:Brent 算法(最常用)、牛顿-拉夫逊法、二分法 |
| 数值积分 (Integration) | UnivariateIntegratorSimpsonIntegratorRombergIntegrator | 计算定积分
∫ a b f ( x ) d x \\int_{a}^{b} f(x) dx ∫abf(x)dx。包括:辛普森法(Simpson)、罗姆贝格法(Romberg)、高斯-勒让德积分 |
5.2、使用示例
5.2.1、数据插值(Interpolation)
插值的作用是:利用已知的离散点集
(
x
0
,
y
0
)
,
(
x
1
,
y
1
)
,
…
(x_0, y_0), (x_1, y_1), \\dots
(x0,y0),(x1,y1),…,构造连续函数
f
(
x
)
f(x)
f(x),以便求出任意未知点
x
x
x 处的估算值。最推荐使用 SplineInterpolator(三次样条插值),其拟合出的曲线最平滑。
import org.apache.commons.math3.analysis.interpolation.SplineInterpolator;
import org.apache.commons.math3.analysis.interpolation.UnivariateInterpolator;
import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction;
public class InterpolationDemo {
public static void main(String[] args) {
// 1. 已知的离散数据点 (注意:x 坐标必须严格单调递增)
double[] x = new double[] { 0.0, 1.0, 2.0, 3.0, 4.0 };
double[] y = new double[] { 0.0, 0.84, 0.91, 0.14, –0.75 }; // 例如 sin(x) 的采样点
// 2. 创建插值器 (三阶样条插值)
UnivariateInterpolator interpolator = new SplineInterpolator();
// 3. 生成拟合好的连续函数对象 PolynomialSplineFunction
PolynomialSplineFunction function = interpolator.interpolate(x, y);
// 4. 求任意中间未知点的插值
double targetX = 1.5;
double predictedY = function.value(targetX);
System.out.println(String.format("当 x = %.1f 时,插值计算得到 y = %.4f", targetX, predictedY));
// 5. 样条函数支持对拟合出的曲线求导
double derivativeValue = function.polynomialSplineDerivative().value(targetX);
System.out.println(String.format("在 x = %.1f 处的导数 (斜率) 为: %.4f", targetX, derivativeValue));
}
}
5.2.2、一元函数求根(Root-Finding / Equation Solving)
求根即求解方程
f
(
x
)
=
0
f(x) = 0
f(x)=0 的解。最常用的求解器是 BrentSolver(结合了二分法、切线法与二次插值法,兼具鲁棒性与收敛速度)。
import org.apache.commons.math3.analysis.UnivariateFunction;
import org.apache.commons.math3.analysis.solvers.BrentSolver;
import org.apache.commons.math3.analysis.solvers.UnivariateSolver;
public class RootFindingDemo {
public static void main(String[] args) {
// 1. 定义要求解的目标函数 f(x) = x^3 – 2x – 5
UnivariateFunction function = new UnivariateFunction() {
@Override
public double value(double x) {
return Math.pow(x, 3) – 2 * x – 5;
}
};
// 2. 创建求解器 (设置绝对容差和相对容差)
UnivariateSolver solver = new BrentSolver(1e-6);
// 3. 在指定区间 [min, max] 内搜寻解 (前提:区间两端点函数值需异号, 即 f(min)*f(max) < 0)
double min = 1.0;
double max = 3.0;
// solve(最大迭代次数, 函数, 下界, 上界)
double root = solver.solve(100, function, min, max);
System.out.println("求解方程 x^3 – 2x – 5 = 0 在 [1, 3] 区间内的根为: " + root);
System.out.println("带入验证 f(root) = " + function.value(root));
}
}
求解方程 x^3 – 2x – 5 = 0 在 [1, 3] 区间内的根为: 2.0945514674640098
带入验证 f(root) = –1.571342558293054E-7
5.2.3、数值积分(Numerical Integration)
计算定积分
∫
a
b
f
(
x
)
d
x
\\int_{a}^{b} f(x) dx
∫abf(x)dx。在函数解析式无法求导积分时,数值积分是唯一的计算手段。推荐使用 SimpsonIntegrator(辛普森法) 或 RombergIntegrator(罗姆贝格积分)。
import org.apache.commons.math3.analysis.UnivariateFunction;
import org.apache.commons.math3.analysis.integration.RombergIntegrator;
import org.apache.commons.math3.analysis.integration.SimpsonIntegrator;
import org.apache.commons.math3.analysis.integration.UnivariateIntegrator;
public class IntegrationDemo {
public static void main(String[] args) {
// 1. 定义被积函数 f(x) = sin(x)
UnivariateFunction function = x -> Math.sin(x);
// 2. 创建辛普森积分器
UnivariateIntegrator integrator = new SimpsonIntegrator();
// 3. 计算积分区间 [0, π] 上的定积分 (理论精确值为 2.0)
double lowerBound = 0.0;
double upperBound = Math.PI;
// integrate(最大评估次数, 函数, 下限, 上限)
double result = integrator.integrate(1000, function, lowerBound, upperBound);
System.out.println(String.format("∫[0, π] sin(x) dx 的数值积分结果: %.8f", result));
}
}
∫[0, π] sin(x) dx 的数值积分结果: 2.00000006
5.3、避坑指南与最佳实践
- 插值算法的数据要求:
- 所有插值类(如 SplineInterpolator)要求传入的
x
x
x 数组必须严格单调递增(即x
0
<
x
1
<
x
2
…
x_0 < x_1 < x_2 \\dots
x0<x1<x2…),且长度不能重复,否则会抛出 NonMonotonicSequenceException。 - 外推风险(Extrapolation):插值函数对象在超出初始数据区间
[
x
min
,
x
max
]
[x_{\\text{min}}, x_{\\text{max}}]
[xmin,xmax] 进行调用时,可能会导致严重失真或抛出 OutOfRangeException,请确保查询点在初始输入区间内。
- 所有插值类(如 SplineInterpolator)要求传入的
- 求根求解器的区间限制:
- 基于区间划分的求解器(如 BrentSolver、BisectionSolver)要求输入的求解区间区间点必须满足
f
(
min
)
⋅
f
(
max
)
≤
0
f(\\text{min}) \\cdot f(\\text{max}) \\le 0
f(min)⋅f(max)≤0。如果不满足,求解器会抛出 NoBracketingException。
- 基于区间划分的求解器(如 BrentSolver、BisectionSolver)要求输入的求解区间区间点必须满足
- 精度与最大迭代数:
- 数值求根和数值积分均属于迭代算法,务必传入合理的最大评估次数(Max Evaluations)(如 1000),避免因函数不收敛或存在间断点而导致死循环。
6、优化与拟合 (Optim)
在 commons-math3 中,优化(Optimization)与曲线拟合(Curve Fitting) 是其最强大的功能之一。该模块集中在 org.apache.commons.math3.optim 和 org.apache.commons.math3.fitting 包下,支持求解一元/多元函数的无约束优化、有约束优化(如线性规划)以及非线性最小二乘曲线拟合。
6.1、核心 API 与模块分类
| 一元函数优化 | BrentOptimizerUnivariateOptimizer | Brent 算法(三点黄金分割+二次插值) | 寻找一元函数
f ( x ) f(x) f(x) 在指定区间内的极小值/极大值 |
| 多元无约束优化 | SimplexOptimizerCMAESOptimizer | Nelder-Mead Simplex(单纯形法)、CMA-ES(进化策略) | 求解多元函数
f ( x 1 , x 2 , … , x n ) f(x_1, x_2, \\dots, x_n) f(x1,x2,…,xn) 的全局/局部极值 |
| 线性规划 (LP) | SimplexSolverLinearConstraint | 单纯形法(Simplex Algorithm) | 满足线性约束条件下的线性目标函数极值求解 |
| 非线性曲线拟合 | SimpleCurveFitterPolynomialCurveFitterGaussianCurveFitter | 基于 Levenberg-Marquardt 或 Gauss-Newton 的非线性最小二乘法 | 给定已知点集,拟合任意自定义函数、多项式或高斯分布 |
6.2、使用示例
6.2.1、一元函数极值优化 (BrentOptimizer)
用于寻找一元连续函数
f
(
x
)
f(x)
f(x) 在指定区间内的极小值(若求极大值,可通过将函数取反 -f(x) 转换)。
import org.apache.commons.math3.analysis.UnivariateFunction;
import org.apache.commons.math3.optim.MaxEval;
import org.apache.commons.math3.optim.nonlinear.univariate.BrentOptimizer;
import org.apache.commons.math3.optim.nonlinear.univariate.SearchInterval;
import org.apache.commons.math3.optim.nonlinear.univariate.UnivariateObjectiveFunction;
import org.apache.commons.math3.optim.nonlinear.univariate.UnivariatePointValuePair;
import org.apache.commons.math3.optim.nonlinear.univariate.GoalType;
public class UnivariateOptimizerDemo {
public static void main(String[] args) {
// 1. 定义目标函数: f(x) = (x – 3)^2 + 5 (极小值点在 x = 3, min_val = 5)
UnivariateFunction function = x -> Math.pow(x – 3, 2) + 5;
// 2. 创建 Brent 优化器 (相对容差, 绝对容差)
BrentOptimizer optimizer = new BrentOptimizer(1e-6, 1e-10);
// 3. 执行优化求解
UnivariatePointValuePair result = optimizer.optimize(
new MaxEval(100), // 最大评估次数
new UnivariateObjectiveFunction(function), // 目标函数
GoalType.MINIMIZE, // 极小值优化 (或 MAXIMIZE)
new SearchInterval(0, 10) // 搜索区间 [0, 10]
);
System.out.println(String.format("极小值点 x = %.6f", result.getPoint()));
System.out.println(String.format("最小值 f(x) = %.6f", result.getValue()));
}
}
6.2.2、非线性多项式曲线拟合 (PolynomialCurveFitter)
用于对给定的二维样本点集合拟合出指定阶数的多项式曲线
y
=
a
0
+
a
1
x
+
a
2
x
2
+
⋯
+
a
n
x
n
y = a_0 + a_1 x + a_2 x^2 + \\dots + a_n x^n
y=a0+a1x+a2x2+⋯+anxn。
import org.apache.commons.math3.fitting.WeightedObservedPoints;
import org.apache.commons.math3.fitting.PolynomialCurveFitter;
public class PolynomialFittingDemo {
public static void main(String[] args) {
// 1. 构建离散观察点集
WeightedObservedPoints points = new WeightedObservedPoints();
// 模拟生成带噪声的数据,真实二次方程: y = 2x^2 + 3x + 5
points.add(1.0, 10.1);
points.add(2.0, 19.0);
points.add(3.0, 32.2);
points.add(4.0, 48.8);
points.add(5.0, 70.1);
// 2. 创建 2 阶多项式拟合器 (二次曲线)
PolynomialCurveFitter fitter = PolynomialCurveFitter.create(2);
// 3. 执行拟合,返回系数数组 [a0, a1, a2]
double[] coeff = fitter.fit(points.toList());
System.out.println("— 拟合多项式系数 —");
System.out.println(String.format("拟合方程: y = %.4fx^2 + %.4fx + %.4f", coeff[2], coeff[1], coeff[0]));
}
}
—– 拟合多项式系数 —–
拟合方程: y = 2.0143x^2 + 2.8943x + 5.2000
6.2.3、自定义函数非线性曲线拟合 (AbstractCurveFitter / ParametricUnivariateFunction)
当默认的多项式或高斯拟合无法满足需求时,可以自定义任意复杂的非线性参数化函数(如指数衰减函数
y
=
a
⋅
e
−
b
⋅
x
+
c
y = a \\cdot e^{-b \\cdot x} + c
y=a⋅e−b⋅x+c)进行参数估计。
import org.apache.commons.math3.analysis.ParametricUnivariateFunction;
import org.apache.commons.math3.fitting.AbstractCurveFitter;
import org.apache.commons.math3.fitting.WeightedObservedPoints;
import org.apache.commons.math3.fitting.leastsquares.LeastSquaresBuilder;
import java.util.Collection;
public class CustomCurveFittingDemo {
// 1. 定义自定义参数化函数: f(x, [a, b, c]) = a * exp(-b * x) + c
static class ExponentialFunction implements ParametricUnivariateFunction {
@Override
public double value(double x, double... parameters) {
double a = parameters[0];
double b = parameters[1];
double c = parameters[2];
return a * Math.exp(–b * x) + c;
}
// 计算函数对各个参数 [a, b, c] 的偏导数 (梯度)
@Override
public double[] gradient(double x, double... parameters) {
double a = parameters[0];
double b = parameters[1];
return new double[] {
Math.exp(–b * x), // ∂f/∂a
–a * x * Math.exp(–b * x),// ∂f/∂b
1.0 // ∂f/∂c
};
}
}
// 2. 包装自定义拟合器
static class ExponentialCurveFitter extends AbstractCurveFitter {
private final double[] initialGuess;
public ExponentialCurveFitter(double[] initialGuess) {
this.initialGuess = initialGuess;
}
@Override
protected LeastSquaresBuilder getProblem(Collection<org.apache.commons.math3.fitting.WeightedObservedPoint> points) {
return getProblem(new ExponentialFunction(), points, initialGuess);
}
}
public static void main(String[] args) {
// 构建模拟数据点,真实模型: y = 5.0 * exp(-0.5 * x) + 1.0
WeightedObservedPoints points = new WeightedObservedPoints();
points.add(0.0, 6.0);
points.add(1.0, 4.03);
points.add(2.0, 2.84);
points.add(3.0, 2.12);
points.add(4.0, 1.68);
// 设置初始猜想值 [a_guess, b_guess, c_guess] (对非线性拟合至关重要)
double[] initialGuess = new double[] { 1.0, 1.0, 1.0 };
ExponentialCurveFitter fitter = new ExponentialCurveFitter(initialGuess);
// 执行拟合
double[] bestParams = fitter.fit(points.toList());
System.out.println("— 自定义指数函数拟合参数结果 —");
System.out.println(String.format("a = %.4f (真实值 ≈ 5.0)", bestParams[0]));
System.out.println(String.format("b = %.4f (真实值 ≈ 0.5)", bestParams[1]));
System.out.println(String.format("c = %.4f (真实值 ≈ 1.0)", bestParams[2]));
}
}
6.2.4、线性规划求解 (SimplexSolver)
求解在满足一组线性约束(<=, >=, =)的条件下,目标函数的最大值或最小值:
极值目标:
max
z
=
3
x
1
+
5
x
2
\\max z = 3x_1 + 5x_2
maxz=3x1+5x2
约束条件:
{
x
1
≤
4
2
x
2
≤
12
3
x
1
+
2
x
2
≤
18
x
1
,
x
2
≥
0
\\begin{cases} x_1 \\le 4 \\\\ 2x_2 \\le 12 \\\\ 3x_1 + 2x_2 \\le 18 \\\\ x_1, x_2 \\ge 0 \\end{cases}
⎩
⎨
⎧x1≤42x2≤123x1+2x2≤18x1,x2≥0
import org.apache.commons.math3.optim.PointValuePair;
import org.apache.commons.math3.optim.nonlinear.scalar.GoalType;
import org.apache.commons.math3.optim.linear.*;
import java.util.ArrayList;
import java.util.Collection;
public class LinearProgrammingDemo {
public static void main(String[] args) {
// 1. 目标函数: 3*x1 + 5*x2 + 0 (最后一个参数为常数偏移量)
LinearObjectiveFunction f = new LinearObjectiveFunction(new double[] { 3.0, 5.0 }, 0);
// 2. 构建线性约束条件
Collection<LinearConstraint> constraints = new ArrayList<>();
constraints.add(new LinearConstraint(new double[] { 1.0, 0.0 }, Relationship.LEQ, 4)); // x1 <= 4
constraints.add(new LinearConstraint(new double[] { 0.0, 2.0 }, Relationship.LEQ, 12)); // 2*x2 <= 12
constraints.add(new LinearConstraint(new double[] { 3.0, 2.0 }, Relationship.LEQ, 18)); // 3*x1 + 2*x2 <= 18
// 3. 求解器初始化与计算
SimplexSolver solver = new SimplexSolver();
PointValuePair solution = solver.optimize(
f,
new LinearConstraintSet(constraints),
GoalType.MAXIMIZE, // 极大值
NonNegativeConstraint.NON_NEGATIVE // 变量非负约束 (x1, x2 >= 0)
);
double[] point = solution.getPoint();
System.out.println(String.format("最优解: x1 = %.2f, x2 = %.2f", point[0], point[1]));
System.out.println(String.format("目标函数最大值 z = %.2f", solution.getValue()));
}
}
6.3、避坑指南与最佳实践
- 初始猜想值(Initial Guess)对非线性拟合至关重要:
- 使用 SimpleCurveFitter 或自定义拟合器时,非线性优化算法(如 Levenberg-Marquardt)极度依赖 initialGuess。若初始猜想值距离真实解太远,算法可能会收敛到局部极小值或抛出 TooManyEvaluationsException。
- 偏导数/梯度计算:
- 在自定义 ParametricUnivariateFunction 时,务必准确推导并实现 gradient() 方法。如果导数计算有误,最小二乘求解器将无法沿着正确梯度下降。
- 无解与无界约束(线性规划):
- 在使用 SimplexSolver 时,如果约束条件互相冲突,会抛出 NoFeasibleSolutionException(无可行解);如果可行域无界,则会抛出 UnboundedSolutionException。实际业务代码中建议做好捕获异常处理。
7、分布与概率 (Distribution)
在 commons-math3 中,概率分布(Probability Distributions) 模块提供了极其丰富且统一的 API。该模块集中在 org.apache.commons.math3.distribution 包下,涵盖了常用的连续型概率分布和离散型概率分布。
7.1、核心 API 体系
所有的概率分布均遵循统一的接口设计:
RealDistribution (连续型接口)
╱
Distribution ───
╲
IntegerDistribution (离散型接口)
核心通用 API 方法
| density(double x) | 概率密度函数 PDF
f ( x ) f(x) f(x) |
连续型分布 |
| probability(int x) | 概率质量函数 PMF
P ( X = x ) P(X = x) P(X=x) |
离散型分布 |
| cumulativeProbability(x) | 累积分布函数 CDF
P ( X ≤ x ) P(X \\le x) P(X≤x) |
两者通用 |
| inverseCumulativeProbability§ | 逆累积分布函数 Quantile / PPF(已知概率
p p p,求阈值 x x x) |
两者通用 |
| sample() / sample(int size) | 生成符合该分布规范的随机数 / 随机样本数组 | 两者通用 |
| getNumericalMean() | 获取该分布的理论期望值(均值) | 两者通用 |
| getNumericalVariance() | 获取该分布的理论方差 | 两者通用 |
常用分布实现类列表
- istribution(指数分布)、UniformRealDistribution(均匀分布)、TDistribution(t 分布)、ChiSquaredDistribution(卡方分布)、GammaDistribution 等。
- 离散型分布 (IntegerDistribution):BinomialDistribution(二项分布)、PoissonDistribution(泊松分布)、UniformIntegerDistribution(离散均匀分布)、GeometricDistribution(几何分布)等。
7.2、使用示例
7.2.1、连续型分布示例:正态分布 (NormalDistribution)
正态分布
N
(
μ
,
σ
2
)
N(\\mu, \\sigma^2)
N(μ,σ2) 在假设检验、置信区间计算以及置信度评估中极其常见。
import org.apache.commons.math3.distribution.NormalDistribution;
import org.apache.commons.math3.distribution.RealDistribution;
public class NormalDistributionDemo {
public static void main(String[] args) {
// 1. 创建标准正态分布 N(μ=0, σ=1)
// 也可以指定参数,如 new NormalDistribution(100.0, 15.0)
RealDistribution normal = new NormalDistribution(0.0, 1.0);
// 2. 计算 x = 0 处的概率密度 (PDF)
double pdfAtZero = normal.density(0.0);
System.out.println("x=0 处的概率密度 f(0): " + pdfAtZero); // ≈ 0.3989
// 3. 计算累积分布概率 (CDF): P(X <= 1.96)
double cdfAt196 = normal.cumulativeProbability(1.96);
System.out.println("P(X <= 1.96) 的概率: " + cdfAt196); // ≈ 0.975 (双侧 95% 置信区间右边界)
// 4. 计算区间概率: P(-1.96 <= X <= 1.96)
double probInterval = normal.cumulativeProbability(–1.96, 1.96);
System.out.println("P(-1.96 <= X <= 1.96) 的概率: " + probInterval); // ≈ 0.95
// 5. 分位数/逆累积分布 (Inverse CDF): 已知 p = 0.95,求 x 使得 P(X <= x) = 0.95
double quantile95 = normal.inverseCumulativeProbability(0.95);
System.out.println("95% 分位数 (Quantile): " + quantile95); // ≈ 1.6448
// 6. 采采样/生成符合该分布的随机数
double singleSample = normal.sample();
double[] sampleArray = normal.sample(5); // 一次性生成 5 个随机数
System.out.println("单次随机采样值: " + singleSample);
}
}
x=0 处的概率密度 f(0): 0.3989422804014327
P(X <= 1.96) 的概率: 0.9750021048517796
P(–1.96 <= X <= 1.96) 的概率: 0.9500042097035591
95% 分位数 (Quantile): 1.6448536269514724
单次随机采样值: 0.2813970442904183
7.2.2、离散型分布示例:泊松分布 (PoissonDistribution)
泊松分布常用于预测在特定时间间隔内某种事件发生的次数(如每小时网站访问量、客服接听电话数)。
import org.apache.commons.math3.distribution.IntegerDistribution;
import org.apache.commons.math3.distribution.PoissonDistribution;
public class PoissonDistributionDemo {
public static void main(String[] args) {
// 假设某服务接口平均每分钟收到 10 次请求 (λ = 10)
IntegerDistribution poisson = new PoissonDistribution(10.0);
// 1. 计算单点概率 (PMF): 恰好收到 8 次请求的概率 P(X = 8)
double pExact8 = poisson.probability(8);
System.out.println("恰好收到 8 次请求的概率 P(X = 8): " + pExact8);
// 2. 计算累积概率 (CDF): 收到不超过 5 次请求的概率 P(X <= 5)
double pAtMost5 = poisson.cumulativeProbability(5);
System.out.println("不超过 5 次请求的概率 P(X <= 5): " + pAtMost5);
// 3. 计算区间概率: 收到 10 到 15 次请求的概率 P(10 <= X <= 15)
double pBetween10And15 = poisson.cumulativeProbability(10, 15);
System.out.println("收到 10~15 次请求的概率: " + pBetween10And15);
// 4. 获取理论均值与方差
System.out.println("理论均值 (Mean): " + poisson.getNumericalMean()); // 10.0
System.out.println("理论方差 (Variance): " + poisson.getNumericalVariance()); // 10.0
}
}
恰好收到 8 次请求的概率 P(X = 8): 0.11259903214901994
不超过 5 次请求的概率 P(X <= 5): 0.0670859628790319
收到 10~15 次请求的概率: 0.36821984650300166
理论均值 (Mean): 10.0
理论方差 (Variance): 10.0
7.2.3、离散型分布示例:二项分布 (BinomialDistribution)
二项分布用于
n
n
n 次独立重复的伯努利试验(如抛硬币,或广告点击率测算)。
import org.apache.commons.math3.distribution.BinomialDistribution;
public class BinomialDistributionDemo {
public static void main(String[] args) {
// 进行 20 次试验,每次成功的概率为 0.3 (n = 20, p = 0.3)
BinomialDistribution binomial = new BinomialDistribution(20, 0.3);
// 1. 恰好成功 6 次的概率 P(X = 6)
double pExact6 = binomial.probability(6);
System.out.println("恰好成功 6 次的概率: " + pExact6);
// 2. 成功次数大于等于 10 次的概率 P(X >= 10) = 1 – P(X <= 9)
double pAtLeast10 = 1.0 – binomial.cumulativeProbability(9);
System.out.println("至少成功 10 次的概率: " + pAtLeast10);
}
}
恰好成功 6 次的概率: 0.19163898275344263
至少成功 10 次的概率: 0.04796189733134337
7.3、高级用法:自定义伪随机种子
在模拟实验(Monte Carlo Simulation)或单元测试中,我们往往需要复现随机结果。可以通过向构造函数传入自定义的随机数生成器(RandomGenerator,如 Well19937c 或 MersenneTwister)并设定 fixed seed:
import org.apache.commons.math3.distribution.NormalDistribution;
import org.apache.commons.math3.random.MersenneTwister;
import org.apache.commons.math3.random.RandomGenerator;
public class SeededDistributionDemo {
public static void main(String[] args) {
// 1. 使用高质量的 MersenneTwister 算法并固定 Seed
RandomGenerator rng = new MersenneTwister(12345L);
// 2. 将随机数生成器传入分布类中
NormalDistribution seededNormal = new NormalDistribution(rng, 0.0, 1.0);
// 3. 此时生成的随机数序列是固定且可复现的
System.out.println("可复现采样点 1: " + seededNormal.sample());
System.out.println("可复现采样点 2: " + seededNormal.sample());
}
}
import org.apache.commons.math3.distribution.NormalDistribution;
import org.apache.commons.math3.random.MersenneTwister;
import org.apache.commons.math3.random.RandomGenerator;
public class SeededDistributionDemo {
public static void main(String[] args) {
// 1. 使用高质量的 MersenneTwister 算法并固定 Seed
RandomGenerator rng = new MersenneTwister(12345L);
// 2. 将随机数生成器传入分布类中
NormalDistribution seededNormal = new NormalDistribution(rng, 0.0, 1.0);
// 3. 此时生成的随机数序列是固定且可复现的
System.out.println("可复现采样点 1: " + seededNormal.sample());
System.out.println("可复现采样点 2: " + seededNormal.sample());
}
}
7.4、避坑指南与最佳实践
- 概率密度 (PDF) vs 概率质量 (PMF):
- 连续分布使用 density(x),其返回值是密度而不是概率(可能大于 1.0)。计算连续分布在某一点的概率恒等于 0,必须通过区间累积概率 cumulativeProbability(x1, x2) 计算概率。
- 离散分布使用 probability(x),其返回值就是具体的概率值
P
(
X
=
x
)
P(X = x)
P(X=x)(范围在0.0
∼
1.0
0.0 \\sim 1.0
0.0∼1.0)。
- 极端值与精度陷阱:
- 在正态分布或指数分布的极深尾部(例如
x
>
10
x > 10
x>10 的标准正态分布),直接计算 cumulativeProbability 可能会因为浮点精度丢失而返回 1.0。如需高度精准的尾部概率,可使用对数概率方法(如 density 配合 log 运算,或部分分布提供的 logDensity)。
- 在正态分布或指数分布的极深尾部(例如
8、复数与数值计算 (Complex/Util)
Apache Commons Math(commons-math3)在复数运算(Complex Numbers)与基础数值计算(Numerical Utilities)方面提供了高精度、符合 IEEE 754 标准且功能完备的 API。核心类分布在 org.apache.commons.math3.complex 和 org.apache.commons.math3.util 包中。
8.1、核心 API 体系
| 复数对象 | Complex | 不可变对象(Immutable),实现了 FieldElement,提供加减乘除、三角函数、指数对数等复数运算 |
| 复数快速傅里叶变换 | FastFourierTransformer | 提供一维/多维离散傅里叶变换(FFT),支持正变换与逆变换(IFFT) |
| 高精度数学工具 | FastMath | 替代 java.lang.Math 的高精度、高性能数学工具类,优化了极值、三角函数和指数运算 |
| 精确算术与组合数 | ArithmeticUtils | 提供了支持**溢出检查(Overflow-checking)**的整数运算,以及最大公约数、最小公倍数、阶乘和组合数 |
| 精细浮点数比较 | Precision | 用于浮点数(double/float)的安全比较,支持指定绝对/相对容差或ULPs(最小表示单位) |
8.2、使用示例
8.2.1、复数基础与高级运算 (Complex)
Complex 是不可变类,每次运算都会返回一个新的 Complex 实例。支持实部、虚部的提取,模长(Magnitude/Abs)、辐角(Argument/Arg)、共轭(Conjugate)以及超越函数(
sin
,
cos
,
exp
,
ln
,
⋅
\\sin, \\cos, \\exp, \\ln, \\sqrt{\\cdot}
sin,cos,exp,ln,⋅
等)计算。
import org.apache.commons.math3.complex.Complex;
public class ComplexDemo {
public static void main(String[] args) {
// 1. 创建复数对象: z1 = 3 + 4i, z2 = 1 – 2i
Complex z1 = new Complex(3.0, 4.0);
Complex z2 = new Complex(1.0, –2.0);
// 2. 基础代数运算
Complex sum = z1.add(z2); // (3+1) + (4-2)i = 4 + 2i
Complex diff = z1.subtract(z2); // (3-1) + (4-(-2))i = 2 + 6i
Complex prod = z1.multiply(z2); // (3+4i)*(1-2i) = 11 – 2i
Complex div = z1.divide(z2); // (3+4i)/(1-2i) = -1 + 2i
System.out.println("z1 + z2 = " + sum);
System.out.println("z1 * z2 = " + prod);
System.out.println("z1 / z2 = " + div);
// 3. 模长 (Magnitude/Abs) 与辐角 (Argument/Phase in Radians)
double abs = z1.abs(); // |3+4i| = √(3^2 + 4^2) = 5.0
double arg = z1.getArgument(); // arctan(4/3)
System.out.println("|z1| = " + abs);
System.out.println("Arg(z1) = " + arg + " rad");
// 4. 共轭复数 (Conjugate)
Complex conjugate = z1.conjugate(); // 3 – 4i
System.out.println("z1 的共轭 = " + conjugate);
// 5. 超越函数: e^z1, ln(z1), √z1
Complex expZ1 = z1.exp();
Complex sinZ1 = z1.sin();
Complex sqrtZ1 = z1.sqrt();
System.out.println("e^z1 = " + expZ1);
System.out.println("sin(z1) = " + sinZ1);
System.out.println("√z1 = " + sqrtZ1);
// 6. 静态特殊常数
System.out.println("虚数单位 i = " + Complex.I);
System.out.println("NaN 复数 = " + Complex.NaN);
}
}
z1 + z2 = (4.0, 2.0)
z1 * z2 = (11.0, –2.0)
z1 / z2 = (–1.0, 2.0)
|z1| = 5.0
Arg(z1) = 0.9272952180016122 rad
z1 的共轭 = (3.0, –4.0)
e^z1 = (–13.128783081462158, –15.200784463067954)
sin(z1) = (3.853738037919377, –27.016813258003936)
√z1 = (2.0, 1.0)
虚数单位 i = (0.0, 1.0)
NaN 复数 = (NaN, NaN)
8.2.2、快速傅里叶变换 (FastFourierTransformer)
用于频域分析和信号处理。注意:FastFourierTransformer 要求输入的数据长度
N
N
N 必须是
2
2
2 的幂次方(Power of 2)(如 8, 16, 64, 1024 等)。
import org.apache.commons.math3.complex.Complex;
import org.apache.commons.math3.transform.DftNormalization;
import org.apache.commons.math3.transform.FastFourierTransformer;
import org.apache.commons.math3.transform.TransformType;
public class FftDemo {
public static void main(String[] args) {
// 1. 准备样本数据 (长度必须是 2 的 N 次幂,此处 N = 8)
double[] timeSeriesSignal = new double[] { 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0 };
// 2. 创建 FFT 变换器 (指定归一化模式,STANDARD 为标准 DFT 模式)
FastFourierTransformer fft = new FastFourierTransformer(DftNormalization.STANDARD);
// 3. 执行正向 FFT (时域 -> 频域)
Complex[] frequencyDomain = fft.transform(timeSeriesSignal, TransformType.FORWARD);
System.out.println("— 频域复数响应结果 —");
for (int i = 0; i < frequencyDomain.length; i++) {
System.out.println(String.format("频点 [%d]: %s, 模长 = %.4f",
i, frequencyDomain[i], frequencyDomain[i].abs()));
}
// 4. 执行逆向 IFFT (频域 -> 时域)
Complex[] restoredSignal = fft.transform(frequencyDomain, TransformType.INVERSE);
System.out.println("\\n— IFFT 还原时域信号 (实部) —");
for (Complex c : restoredSignal) {
System.out.println(String.format("%.2f", c.getReal()));
}
}
}
—– 频域复数响应结果 —–
频点 [0]: (4.0, 0.0), 模长 = 4.0000
频点 [1]: (1.0, –2.414213562373095), 模长 = 2.6131
频点 [2]: (0.0, 0.0), 模长 = 0.0000
频点 [3]: (1.0000000000000004, –0.41421356237309503), 模长 = 1.0824
频点 [4]: (0.0, 0.0), 模长 = 0.0000
频点 [5]: (0.9999999999999999, 0.4142135623730949), 模长 = 1.0824
频点 [6]: (0.0, 0.0), 模长 = 0.0000
频点 [7]: (0.9999999999999997, 2.414213562373095), 模长 = 2.6131
—– IFFT 还原时域信号 (实部) —–
1.00
1.00
1.00
1.00
0.00
0.00
0.00
0.00
8.2.3、安全算术与组合数 (ArithmeticUtils)
在进行阶乘、乘方或大数计算时,传统的 Java 原生算术非常容易遭遇整数溢出(Overflow)而不报错(变成负数)。ArithmeticUtils 提供了严格的溢出检测。
import org.apache.commons.math3.util.ArithmeticUtils;
public class ArithmeticUtilsDemo {
public static void main(String[] args) {
// 1. 最大公约数 (GCD) 与 最小公倍数 (LCM)
int gcd = ArithmeticUtils.gcd(24, 36); // 12
int lcm = ArithmeticUtils.lcm(24, 36); // 72
System.out.println("GCD(24, 36) = " + gcd + " | LCM(24, 36) = " + lcm);
// 2. 组合数 C(n, k) 与 阶乘 n!
long combinations = ArithmeticUtils.binomialCoefficient(10, 3); // C(10, 3) = 120
long factorial = ArithmeticUtils.factorial(10); // 10! = 3628800
System.out.println("C(10, 3) = " + combinations);
System.out.println("10! = " + factorial);
// 3. 带溢出检查的加减乘除 (溢出时抛出 MathArithmeticException)
try {
int safeResult = ArithmeticUtils.addAndCheck(Integer.MAX_VALUE, 1);
} catch (ArithmeticException e) {
System.out.println("捕获到整数溢出异常: " + e.getMessage());
}
// 4. 判断是否为 2 的幂次方
boolean isPowerOfTwo = ArithmeticUtils.isPowerOfTwo(64); // true
System.out.println("64 是否为 2 的幂: " + isPowerOfTwo);
}
}
GCD(24, 36) = 12 | LCM(24, 36) = 72
C(10, 3) = 120
10! = 3628800
捕获到整数溢出异常: overflow in addition: 2,147,483,647 + 1
64 是否为 2 的幂: true
8.2.4、高精度浮点数安全比较 (Precision)
由于计算机二进制浮点数保存机制(IEEE 754),像 0.1 + 0.2 == 0.3 在 Java 原生比较中会返回 false。Precision 提供了极其严格且灵活的比较工具:
import org.apache.commons.math3.util.Precision;
public class PrecisionDemo {
public static void main(String[] args) {
double a = 0.1 + 0.2;
double b = 0.3;
System.out.println("原生直接比较 (a == b): " + (a == b)); // false
// 1. 使用绝对容差 (Epsilon) 比较是否相等
boolean equalsByEpsilon = Precision.equals(a, b, 1e-9);
System.out.println("使用 Epsilon 容差比较: " + equalsByEpsilon); // true
// 2. 使用 ULP (Unit in the Last Place) 最小表示单位数比较
// 允许允许最后 1 个或 2 个 ULP 差异
boolean equalsByUlp = Precision.equals(a, b, 1);
System.out.println("使用 1 个 ULP 容差比较: " + equalsByUlp); // true
// 3. 安全保留小数位数 (Rounding)
double rounded = Precision.round(1.234567, 3); // 1.235
System.out.println("保留 3 位小数: " + rounded);
// 4. 浮点数比大小 (返回 -1, 0, 1)
int compareResult = Precision.compareTo(a, b, 1e-9);
System.out.println("比较结果: " + compareResult); // 0 (表示相等)
}
}
原生直接比较 (a == b): false
使用 Epsilon 容差比较: true
使用 1 个 ULP 容差比较: true
保留 3 位小数: 1.235
比较结果: 0
8.2.5、高性能数学计算 (FastMath)
FastMath 是 java.lang.Math 和 StrictMath 的直接替代品。它通过查表法(Lookup tables)和多项式逼近重写了大量数学函数,在保证高精度的同时提高了吞吐量。
import org.apache.commons.math3.util.FastMath;
public class FastMathDemo {
public static void main(String[] args) {
double val = 2.0;
// 常用数学计算
double sqrt = FastMath.sqrt(val);
double log = FastMath.log(val);
double pow = FastMath.pow(val, 3);
double sin = FastMath.sin(FastMath.PI / 6.0); // sin(30°) = 0.5
System.out.println("FastMath.sqrt(2) = " + sqrt);
System.out.println("FastMath.sin(π/6) = " + sin);
}
}
FastMath.sqrt(2) = 1.4142135623730951
FastMath.sin(π/6) = 0.49999999999999994
8.3、避坑指南与最佳实践
- Complex 对象的不可变性与 NaN 处理:
- Complex 运算不会改变原对象,必须接收其返回值(如 z = z.add(z2))。
- 如果复数计算中产生了除以零等非法操作,Complex 不会抛出 ArithmeticException,而是会返回一个 Complex.NaN 实例。可以通过 z.isNaN() 或 z.isInfinite() 进行状态校验。
- FFT 输入数组长度要求:
- FastFourierTransformer 严格要求输入的数组长度必须是
2
n
2^n
2n(如 2, 4, 8, 16, 32, 64…)。如果输入非2
n
2^n
2n 长度的数据,会抛出 MathIllegalArgumentException。在处理任意长度数据前,应手动对数组末尾補零(Zero-Padding)至最近的2
n
2^n
2n 长度。
- FastFourierTransformer 严格要求输入的数组长度必须是
- 浮点数比较优先使用 Precision:
- 在涉及数值分析、收敛判定或浮点数相等的单元测试中,切勿使用 ==。优先推荐 Precision.equals(a, b, epsilon) 或 Precision.equals(a, b, maxUlps)。


