Apache Commons Math 3.6.1 数值计算示例详解

以可运行的完整示例掌握 Apache Commons Math 3.6.1 数值计算:复数运算、素数判定与分解、分数运算、统计与线性代数组合的最小二乘拟合综合案例,每个示例独立成类并配实测输出与浮点精度解读,全部已在 Ubuntu 22.04 实测。

Apache Commons MathApache Commons Math2026/10/15

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。

💡 技术拓展:三种"精度工具"的取舍(结合本文实测)——

类型表示速度精度特征适用场景
doubleIEEE 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)才可安全比较。

九、参考链接

  1. Apache Commons Math 官方站点:https://commons.apache.org/proper/commons-math/
  2. Commons Math 3.6.1 Javadoc(Complex/Primes/Fraction 类说明):https://commons.apache.org/proper/commons-math/javadocs/api-3.6.1/
  3. 官方用户指南(Numbers、Linear Algebra 章节):https://commons.apache.org/proper/commons-math/userguide/linear.html
  4. Maven 中央仓库坐标页:https://repo1.maven.org/maven2/org/apache/commons/commons-math3/
  5. IEEE 754 标准与 Java 浮点语义:https://docs.oracle.com/javase/specs/jls/se17/html/jls-4.html#jls-4.2.3