Apache Commons Math 3.6.1 数值计算示例详解
以可运行的完整示例掌握 Apache Commons Math 3.6.1 数值计算:复数运算、素数判定与分解、分数运算、统计与线性代数组合的最小二乘拟合综合案例,每个示例独立成类并配实测输出与浮点精度解读,全部已在 Ubuntu 22.04 实测。
Apache Commons Math 3.6.1 数值计算示例详解
目标:通过四个可独立编译运行的完整示例,掌握 Apache Commons Math 3.6.1 的数值计算能力——复数运算(Complex)、素数判定与分解(Primes)、分数运算(Fraction),以及统计+线性代数组合的最小二乘拟合综合案例。每个示例给出完整源码与实测输出,并解读 double 精度表现。以下全部命令与回显均已在 Ubuntu 22.04(x86_64)、OpenJDK 17.0.20.1 实测通过。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- JDK:OpenJDK 17.0.20.1(
java -version实测) - 构建工具:Apache Maven 3.6.3
- 目标库:Apache Commons Math 3.6.1(
org.apache.commons:commons-math3:3.6.1,零传递依赖) - Maven 示例工程目录:
/opt/math-demo,四个示例类均在demo包下 - 运行方式:
java -Dfile.encoding=UTF-8 -cp target/classes:$M3 demo.<类名>($M3在第二章赋值为本地仓库 jar 全路径;-Dfile.encoding=UTF-8保证 POSIX/C locale 下中文输出正常)
确认 JDK 与 Maven 版本基线:
java -version 2>&1
mvn -v | head -3
输出:
openjdk version "17.0.20.1" 2026-08-18
OpenJDK Runtime Environment (build 17.0.20.1+1-1-22.04-Ubuntu)
OpenJDK 64-Bit Server VM (build 17.0.20.1+1-1-22.04-Ubuntu, mixed mode, sharing)
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 17.0.20.1, vendor: Ubuntu, runtime: /usr/lib/jvm/java-17-openjdk-amd64
二、前置条件:工程准备与运行方式
创建工程目录并写入 /opt/math-demo/pom.xml(全文):
mkdir -p /opt/math-demo/src/main/java/demo
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>demo</groupId>
<artifactId>math-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>17</maven.compiler.release>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-math3</artifactId>
<version>3.6.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
</plugin>
</plugins>
</build>
</project>
逐项说明:maven.compiler.release=17 按 JDK 17 语义编译;maven-compiler-plugin 必须显式固定 3.11.0,否则 Maven 3.6.3 默认绑定的 3.1 版插件不认识 release 属性,会报 Source option 5 is no longer supported;依赖只声明 commons-math3:3.6.1 一项(零传递依赖)。
编译一次,后续所有示例共用:
cd /opt/math-demo && mvn -q compile
echo "EXIT=$?"
M3=~/.m2/repository/org/apache/commons/commons-math3/3.6.1/commons-math3-3.6.1.jar
输出:
EXIT=0
四个示例类分别放入 src/main/java/demo/ 后重新 mvn -q compile 即可运行,每个示例的运行命令与输出见对应章节。
三、示例 1:复数运算(Complex)
复数类 org.apache.commons.math3.complex.Complex 是不可变对象:加减乘除等方法都返回新 Complex 实例,原对象不变。/opt/math-demo/src/main/java/demo/ComplexDemo.java 全文:
package demo;
import org.apache.commons.math3.complex.Complex;
public class ComplexDemo {
public static void main(String[] args) {
System.out.println("== Complex ==");
Complex a = new Complex(3, 4);
Complex b = new Complex(1, -2);
System.out.println("a = 3 + 4i, b = 1 - 2i");
System.out.println("a.add(b) = " + a.add(b));
System.out.println("a.subtract(b) = " + a.subtract(b));
System.out.println("a.multiply(b) = " + a.multiply(b));
System.out.println("a.divide(b) = " + a.divide(b));
System.out.println("a.abs() = " + a.abs());
System.out.println("a.pow(2) = " + a.pow(2));
System.out.println("a.conjugate() = " + a.conjugate());
}
}
编译运行:
java -Dfile.encoding=UTF-8 -cp target/classes:$M3 demo.ComplexDemo
输出:
== Complex ==
a = 3 + 4i, b = 1 - 2i
a.add(b) = (4.0, 2.0)
a.subtract(b) = (2.0, 6.0)
a.multiply(b) = (11.0, -2.0)
a.divide(b) = (-1.0, 2.0)
a.abs() = 5.0
a.pow(2) = (-6.999999999999997, 24.0)
a.conjugate() = (3.0, -4.0)
逐项核对(实部记 a.re=3、虚部 a.im=4;b.re=1、b.im=-2):
- add:(3+1, 4-2) = (4, 2);subtract:(3-1, 4+2) = (2, 6)。
- multiply:(3×1-4×(-2), 3×(-2)+4×1) = (11, -2);divide:(3+4i)/(1-2i) = (3+4i)(1+2i)/((1-2i)(1+2i)) = (-5+10i)/5 = (-1, 2),精确。
- abs:√(3²+4²) = 5.0 精确;pow(2) 理论值 (3+4i)² = 9+24i-16 = (-7, 24),实测实部
-6.999999999999997带 3×10⁻¹⁵ 尾差——pow内部经exp(2·log a)路径计算引入浮点误差(详读见第七章)。 - conjugate 共轭 (3, -4);输出格式为
(实部, 虚部)。
四、示例 2:素数判定与分解(Primes)
org.apache.commons.math3.primes.Primes 是静态工具类,提供素数判定、下一个素数与质因数分解。/opt/math-demo/src/main/java/demo/PrimesDemo.java 全文:
package demo;
import org.apache.commons.math3.primes.Primes;
public class PrimesDemo {
public static void main(String[] args) {
System.out.println("== Primes ==");
System.out.println("isPrime(2) = " + Primes.isPrime(2));
System.out.println("isPrime(97) = " + Primes.isPrime(97));
System.out.println("isPrime(100) = " + Primes.isPrime(100));
System.out.println("isPrime(1) = " + Primes.isPrime(1));
System.out.println("nextPrime(97) = " + Primes.nextPrime(97));
System.out.println("nextPrime(98) = " + Primes.nextPrime(98));
System.out.println("nextPrime(100) = " + Primes.nextPrime(100));
System.out.println("primeFactors(360) = " + Primes.primeFactors(360));
System.out.println("primeFactors(97) = " + Primes.primeFactors(97));
System.out.println("primeFactors(1024)= " + Primes.primeFactors(1024));
}
}
编译运行:
java -Dfile.encoding=UTF-8 -cp target/classes:$M3 demo.PrimesDemo
输出:
== Primes ==
isPrime(2) = true
isPrime(97) = true
isPrime(100) = false
isPrime(1) = false
nextPrime(97) = 97
nextPrime(98) = 101
nextPrime(100) = 101
primeFactors(360) = [2, 2, 2, 3, 3, 5]
primeFactors(97) = [97]
primeFactors(1024)= [2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
逐项核对:
isPrime(1)返回 false 符合数论定义(1 不是素数);2 是最小的素数返回 true。nextPrime(97)返回 97 本身(入参已是素数);98 与 100 的下一个素数都是 101。primeFactors返回升序List<Integer>,含重复因子:360 = 2³×3²×5,1024 = 2¹⁰;素数入参返回单元素列表 [97]。- 注意三个方法的入参都是 int;超出 int 范围的素数运算该工具类不支持。
五、示例 3:分数运算(Fraction)
org.apache.commons.math3.fraction.Fraction 以分子/分母两个 int 精确表示有理数,避免 double 舍入。/opt/math-demo/src/main/java/demo/FractionDemo.java 全文:
package demo;
import org.apache.commons.math3.fraction.Fraction;
public class FractionDemo {
public static void main(String[] args) {
System.out.println("== Fraction ==");
Fraction f1 = new Fraction(1, 3);
Fraction f2 = new Fraction(1, 6);
System.out.println("f1 = " + f1 + ", f2 = " + f2);
System.out.println("f1.add(f2) = " + f1.add(f2));
System.out.println("f1.multiply(f2) = " + f1.multiply(f2));
System.out.println("new Fraction(2,4)(构造自动约分) = " + new Fraction(2, 4)
+ " -> " + new Fraction(2, 4).getNumerator() + "/" + new Fraction(2, 4).getDenominator());
System.out.println("f1.doubleValue() = " + f1.doubleValue());
System.out.println("new Fraction(3,8).doubleValue()*100 = " + new Fraction(3, 8).doubleValue() * 100);
System.out.printf("百分数输出(3/8): %.1f%%%n", new Fraction(3, 8).doubleValue() * 100);
}
}
编译运行:
java -Dfile.encoding=UTF-8 -cp target/classes:$M3 demo.FractionDemo
输出:
== Fraction ==
f1 = 1 / 3, f2 = 1 / 6
f1.add(f2) = 1 / 2
f1.multiply(f2) = 1 / 18
new Fraction(2,4)(构造自动约分) = 1 / 2 -> 1/2
f1.doubleValue() = 0.3333333333333333
new Fraction(3,8).doubleValue()*100 = 37.5
百分数输出(3/8): 37.5%
逐项核对:1/3 + 1/6 = 2/6 + 1/6 = 3/6,结果自动约分为 1/2;1/3 × 1/6 = 1/18;new Fraction(2,4) 构造时即约分为 1/2(getNumerator()=1、getDenominator()=2),约分在构造时完成,无需手动调用。
没有 reduce() 与 percentageValue()——替代写法(3.6.1 的 Fraction 没有这两个方法,写它们编译报 cannot find symbol):
- 约分:不需要,构造时已自动完成;运算结果的分母也保持最简。
- 百分数:用
f.doubleValue() * 100,再按需格式化——本例 3/8 → 37.5,实测代码System.out.printf("百分数输出(3/8): %.1f%%%n", new Fraction(3, 8).doubleValue() * 100)输出百分数输出(3/8): 37.5%。
toString() 输出格式为 分子 / 分母(含空格);需要更高精度或大数分子分母时,同包还有 BigFraction(基于 BigInteger)。
六、示例 4:综合案例——统计与线性代数组合(最小二乘拟合)
综合案例:8 个批次的添加剂用量 x 与产品强度 y,先用 DescriptiveStatistics 做质量统计,再用矩阵正规方程 (XᵀX)·b = Xᵀy 求一元线性回归系数,最后预测 x=9 处的强度。全程只使用前两篇已实测的 API:DescriptiveStatistics、Array2DRowRealMatrix、transpose、operate、multiply、LUDecomposition.getSolver().solve、ArrayRealVector.dotProduct。
/opt/math-demo/src/main/java/demo/MathCombo.java 全文:
package demo;
import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
import org.apache.commons.math3.linear.Array2DRowRealMatrix;
import org.apache.commons.math3.linear.ArrayRealVector;
import org.apache.commons.math3.linear.LUDecomposition;
import org.apache.commons.math3.linear.DecompositionSolver;
import org.apache.commons.math3.linear.RealMatrix;
import org.apache.commons.math3.linear.RealVector;
public class MathCombo {
public static void main(String[] args) {
// 场景:8 个批次的添加剂用量 x 与产品强度 y,先做质量统计,再拟合一元线性回归并预测
double[] x = {1, 2, 3, 4, 5, 6, 7, 8};
double[] y = {2.1, 3.9, 6.2, 8.1, 9.8, 12.2, 13.9, 16.1};
// 1) 对强度 y 做描述统计
DescriptiveStatistics ds = new DescriptiveStatistics(y);
System.out.println("N = " + ds.getN());
System.out.println("mean(y) = " + ds.getMean());
System.out.println("sd(y) = " + ds.getStandardDeviation());
System.out.println("p50(y) = " + ds.getPercentile(50));
// 2) 正规方程 (X^T X) b = X^T y 求回归系数 b = [截距, 斜率]
double[][] xd = new double[8][2];
for (int i = 0; i < 8; i++) {
xd[i][0] = 1.0;
xd[i][1] = x[i];
}
RealMatrix X = new Array2DRowRealMatrix(xd, false);
RealMatrix Xt = X.transpose();
RealMatrix XtX = Xt.multiply(X);
RealVector Xty = Xt.operate(new ArrayRealVector(y));
DecompositionSolver solver = new LUDecomposition(XtX).getSolver();
RealVector b = solver.solve(Xty);
System.out.println("XtX = " + XtX);
System.out.println("intercept = " + b.getEntry(0));
System.out.println("slope = " + b.getEntry(1));
// 3) 用拟合结果预测 x = 9 时的强度
RealVector x9 = new ArrayRealVector(new double[] {1.0, 9.0});
System.out.println("predict(9)= " + b.dotProduct(x9));
}
}
说明:new Array2DRowRealMatrix(xd, false) 第二个参数 false 表示直接引用传入数组不做拷贝(数据不再修改时更省内存);回归系数向量 b=[截距, 斜率],预测时把 [1, x] 与 b 做点积即 ŷ = 截距 + 斜率×x。
编译运行:
java -Dfile.encoding=UTF-8 -cp target/classes:$M3 demo.MathCombo
输出:
N = 8
mean(y) = 9.0375
sd(y) = 4.8925416999463875
p50(y) = 8.95
XtX = Array2DRowRealMatrix{{8.0,36.0},{36.0,204.0}}
intercept = 0.053571428571427444
slope = 1.9964285714285717
predict(9)= 18.021428571428572
逐项手算核对:
- 统计:Σy = 72.3,均值 72.3/8 = 9.0375 ✓;中位数 = (8.1+9.8)/2 = 8.95 ✓。
- XtX:XᵀX = [[N, Σx],[Σx, Σx²]] = [[8, 36],[36, 204]],与输出矩阵一致。
- 斜率 slope = Σ(x-x̄)(y-ȳ)/Σ(x-x̄)² = 83.85/42 = 1.9964285714285717 ✓;截距 intercept = ȳ - slope×x̄ = 9.0375 - 1.9964285714285717×4.5 = 0.053571428571427444 ✓。
- 预测 predict(9) = 0.053571428571427444 + 1.9964285714285717×9 = 18.021428571428572 ✓(末位
427444的尾差见第七章)。
这个案例展示了 commons-math3 的典型组合方式:统计门面负责数据画像,线性代数门面负责参数求解,一个 jar 内闭环完成,无需引入其他依赖。
七、double 精度与数值稳定性注意事项
以上实测输出中有几处"看起来不整"的数字,正是 IEEE 754 double 的正常行为。结合实测输出逐个说明,写业务代码时按此建立预期:
-1.4000000000000004(求解方程结果,见第 3 篇 5.2 节):理论值 -1.4 在二进制下不可精确表示,LU 分解的消元顺序带来约 4×10⁻¹⁶ 的相对误差。比较浮点数用容差,例如Math.abs(x - (-1.4)) < 1e-9,禁止直接==。0.9999999999999999(特征值理论值 1,见第 3 篇 5.3 节):数值算法按迭代收敛,误差在 1 ulp(约 2.2×10⁻¹⁶)量级,属正常舍入而非 bug。1.9964285714285717与0.053571428571427444(回归系数,第六章):分数 83.85/42 的 double 表示本身带误差,多步矩阵运算会轻微放大,但相对误差仍在 10⁻¹⁵ 量级,对统计结论无影响。0.3333333333333333(Fraction.doubleValue(),第五章):精确分数转 double 时同样失去精确性——这正是 Fraction 存在的意义:中间运算保持分子/分母精确,只在最终展示时转 double。-6.999999999999997(Complex.pow(2) 实部,第三章):幂运算内部经exp(2·log a)路径,理论值 -7 带出 3×10⁻¹⁵ 尾差。看似"乘一次自己"的整数次幂也会走超越函数路径,精度敏感场景优先用multiply连乘替代小指数pow。
💡 技术拓展:三种"精度工具"的取舍(结合本文实测)——
类型 表示 速度 精度特征 适用场景 double IEEE 754 二进制浮点 最快 相对误差 ~1e-16,存在表示误差(0.1 不精确) 本篇全部示例;科学计算、统计、机器学习默认选择 BigDecimal 任意精度十进制 慢(对象+大数运算) 十进制精确可控,需显式舍入模式 金额、账务等十进制语义场景 Fraction/BigFraction 精确有理数(分子/分母) 中等 加减乘除完全精确,转 double 时才舍入 需要精确比例、避免累积误差的算法场景 另注意两个稳定性习惯:避免对病态矩阵(行列式接近 0)用
solve(),commons-math3 会抛SingularMatrixException,可先检查getDeterminant();大数据求和用 Kahan 求和或DescriptiveStatistics内置的补偿算法,朴素累加在 10⁸ 量级样本上可能损失明显精度。
八、常见问题(FAQ)
Q1:Fraction 调用 reduce() 或 percentageValue() 编译报错怎么办?
实测报错为 cannot find symbol: method reduce() / method percentageValue()。3.6.1 的 Fraction 没有这两个方法:约分在构造时自动完成,百分数用 f.doubleValue() * 100 自行格式化(见第五章替代写法)。
Q2:为什么 1/3 + 1/6 用 Fraction 得到 1 / 2 而用 double 得到 0.5 带尾数?
Fraction 在分子/分母整数域运算并自动约分,结果精确;double 表示 1/3 本身是舍入值(0.3333333333333333),相加后可能带 49999999999999994 之类的尾数。需要精确比例计算(配比、折扣、化学计量)时优先 Fraction(见第五章与第七章)。
Q3:Primes.primeFactors 的结果和数学教材写的 2²×3²×5 形式不一样?
primeFactors 返回升序因子列表(重复因子逐个列出),360 → [2, 2, 2, 3, 3, 5],需要指数形式时自行聚合(如用 Map<Integer, Integer> 统计出现次数)。
Q4:Complex 运算结果可以直接用 equals 比较吗?
不建议。Complex 基于 double 分量,pow/divide 等运算有舍入误差(如第三章实测 pow(2) 实部为 -6.999999999999997 而非精确 -7),比较请对实部/虚部分别用容差 Math.abs(a.re - b.re) < 1e-9。abs() 这类能被毕达哥拉斯精确表示的值(如 5.0)才可安全比较。
九、参考链接
- Apache Commons Math 官方站点:https://commons.apache.org/proper/commons-math/
- Commons Math 3.6.1 Javadoc(Complex/Primes/Fraction 类说明):https://commons.apache.org/proper/commons-math/javadocs/api-3.6.1/
- 官方用户指南(Numbers、Linear Algebra 章节):https://commons.apache.org/proper/commons-math/userguide/linear.html
- Maven 中央仓库坐标页:https://repo1.maven.org/maven2/org/apache/commons/commons-math3/
- IEEE 754 标准与 Java 浮点语义:https://docs.oracle.com/javase/specs/jls/se17/html/jls-4.html#jls-4.2.3