欢迎光临
我们一直在努力

ApacheCommons——commons-math3(科学计算与线性代数)(二)

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,请确保查询点在初始输入区间内。

  • 求根求解器的区间限制:
    • 基于区间划分的求解器(如 BrentSolver、BisectionSolver)要求输入的求解区间区间点必须满足

      f

      (

      min

      )

      f

      (

      max

      )

      0

      f(\\text{min}) \\cdot f(\\text{max}) \\le 0

      f(min)f(max)0。如果不满足,求解器会抛出 NoBracketingException。

  • 精度与最大迭代数:
    • 数值求根和数值积分均属于迭代算法,务必传入合理的最大评估次数(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=aebx+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}

x142x2123x1+2x218x1,x20

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(Xx)

两者通用
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.01.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
使用 1ULP 容差比较: 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 长度。

  • 浮点数比较优先使用 Precision:
    • 在涉及数值分析、收敛判定或浮点数相等的单元测试中,切勿使用 ==。优先推荐 Precision.equals(a, b, epsilon) 或 Precision.equals(a, b, maxUlps)。
赞(0)
未经允许不得转载:171主机测评 » ApacheCommons——commons-math3(科学计算与线性代数)(二)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址