Apache Commons Math 3.6.1 API 与统计代数详解

系统讲解 Apache Commons Math 3.6.1 的统计 API(DescriptiveStatistics、SummaryStatistics、TTest)与线性代数 API(Array2DRowRealMatrix、LUDecomposition、EigenDecomposition、RealVector),逐项代码配真实输出,并给出与 math4 的命名差异锚点,全部已在 Ubuntu 22.04 实测。

Apache Commons MathApache Commons Math2026/10/15

Apache Commons Math 3.6.1 API 与统计代数详解

目标:系统掌握 Apache Commons Math 3.6.1 的两大核心 API 面——统计(DescriptiveStatistics、SummaryStatistics、TTest)与线性代数(Array2DRowRealMatrix、LUDecomposition、EigenDecomposition、RealVector),每个方法都配完整示例代码与实测输出,并标注与 math4 的命名差异。以下全部命令与回显均已在 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,2016-03-21 发布后的最终版本)
  • 传递依赖:无(零传递依赖,mvn dependency:tree 实测仅自身一个节点)
  • Maven 示例工程目录:/opt/math-demo(本文演示类 demo.MathDemo 在该工程内编译运行)

确认 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

💡 说明:本文所有运行命令统一为 java -Dfile.encoding=UTF-8 -cp target/classes:$M3 demo.MathDemo <组名> 形式($M3 在第三章赋值为本地仓库 jar 全路径);-Dfile.encoding=UTF-8 用于 POSIX/C locale 下中文输出不乱码,数字结果不受影响。

二、API 总览

Commons Math 3.6.1 的 API 按「包 → 门面类 → 方法」组织,统计与线性代数分别位于 org.apache.commons.math3.stat 与 org.apache.commons.math3.linear。本文覆盖的核心 API 如下(第一列即调用入口,均经本文实测):

API包角色关键方法详解章节
DescriptiveStatisticsstat.descriptive有存储的描述统计(支持滑动窗口)addValue、getMean、getVariance、getStandardDeviation、getMax、getMin、getPercentile、getSum、getN、removeMostRecentValue4.1、4.2
SummaryStatisticsstat.descriptive无存储的汇总统计(常数内存)addValue、getMean、getVariance、getStandardDeviation、getSum、getN4.3
TTeststat.inferencet 检验pairedT、pairedTTest、homoscedasticT、homoscedasticTTest4.4
Array2DRowRealMatrixlinear行存储实矩阵multiply、transpose、operate、toString5.1
LUDecompositionlinearLU 分解getDeterminant、getSolver5.2
DecompositionSolverlinear分解求解器solve(只收 RealVector/RealMatrix)5.2
EigenDecompositionlinear特征分解getRealEigenvalues、getImagEigenvalues5.3
ArrayRealVector / RealVectorlinear实向量add、dotProduct、getNorm、mapMultiply5.4

⚠️ 注意:3.6.1 是 2016 年的最终版本,与 math4/部分新资料存在 API 名差异(removeMostRecentValue、solve 签名、Fraction 方法等),本文各节遇到时都会给出实测锚点,第六章集中汇总。

💡 技术拓展:math3 与 math4 的包结构对照(迁移评估必看,包名决定共存可行性)——

功能math3 3.6.1 包名math4 4.0-beta1 包名(所在模块)
描述统计/汇总统计org.apache.commons.math3.stat.descriptiveorg.apache.commons.math4.legacy.stat.descriptive(commons-math4-legacy)
t 检验等统计推断org.apache.commons.math3.stat.inferenceorg.apache.commons.math4.legacy.stat.inference(commons-math4-legacy)
线性代数org.apache.commons.math3.linearorg.apache.commons.math4.legacy.linear(commons-math4-legacy)
复数org.apache.commons.math3.complexorg.apache.commons.math4.legacy.complex(commons-math4-legacy)
FastMathorg.apache.commons.math3.util.FastMathorg.apache.commons.math4.legacy.core.jdkmath.AccurateMath(legacy-core,类更名)
极简高精度数学核心无org.apache.commons.math4.core.jdkmath(commons-math4-core,实测仅 18 个类)

两代包名不重叠,可同 classpath 共存过渡;迁移时旧代码多数情况只改顶层包名即可编译,FastMath 需改名 AccurateMath(jar tf 实测 math4-legacy 内 org/apache/commons/math4/legacy/stat/descriptive/DescriptiveStatistics.class 存在)。

三、前置条件:工程与演示主类

创建工程目录并写入 /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 语义编译(math3 自身是 major=49 的 Java 5 字节码,两者互不影响);maven-compiler-plugin 必须显式固定 3.11.0,否则 Maven 3.6.3 默认绑定的 3.1 版插件不认识 release 属性,会报 Source option 5 is no longer supported。

/opt/math-demo/src/main/java/demo/MathDemo.java 全文(本文所有示例集中在这一个主类,按组演示,通过命令行参数选择组):

package demo;

import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
import org.apache.commons.math3.stat.inference.TTest;
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.EigenDecomposition;
import org.apache.commons.math3.linear.DecompositionSolver;
import org.apache.commons.math3.complex.Complex;
import org.apache.commons.math3.primes.Primes;
import org.apache.commons.math3.fraction.Fraction;

public class MathDemo {

    public static void main(String[] args) {
        String group = args.length > 0 ? args[0] : "all";
        if (group.equals("descriptive") || group.equals("all")) descriptive();
        if (group.equals("summary") || group.equals("all")) summary();
        if (group.equals("linalg") || group.equals("all")) linalg();
        if (group.equals("vector") || group.equals("all")) vector();
        if (group.equals("complex") || group.equals("all")) complex();
        if (group.equals("primes") || group.equals("all")) primes();
        if (group.equals("fraction") || group.equals("all")) fraction();
        if (group.equals("ttest") || group.equals("all")) ttest();
    }

    static void descriptive() {
        System.out.println("== DescriptiveStatistics(无界 + 滑动窗口) ==");
        DescriptiveStatistics ds = new DescriptiveStatistics();
        double[] data = {9, 2, 5, 4, 12, 7, 8, 11, 9, 3, 7, 4, 12, 5, 4, 10, 9, 6, 9, 4};
        for (double v : data) {
            ds.addValue(v);
        }
        System.out.println("data.length            = " + data.length);
        System.out.println("getN()                 = " + ds.getN());
        System.out.println("getMean()              = " + ds.getMean());
        System.out.println("getVariance()          = " + ds.getVariance());
        System.out.println("getStandardDeviation() = " + ds.getStandardDeviation());
        System.out.println("getMax()               = " + ds.getMax());
        System.out.println("getMin()               = " + ds.getMin());
        System.out.println("getPercentile(50)      = " + ds.getPercentile(50));
        System.out.println("getSum()               = " + ds.getSum());

        System.out.println("-- 窗口大小 5 的滑动统计 --");
        DescriptiveStatistics window = new DescriptiveStatistics(5);
        for (double v : new double[] {1, 2, 3, 4, 5, 6, 7, 8}) {
            window.addValue(v);
            System.out.printf("add %.0f -> N=%d mean=%.3f min=%.0f max=%.0f%n",
                v, window.getN(), window.getMean(), window.getMin(), window.getMax());
        }
        window.removeMostRecentValue();
        System.out.println("after removeMostRecentValue: N=" + window.getN()
            + " mean=" + window.getMean());
    }

    static void summary() {
        System.out.println("== SummaryStatistics(两个数据集 + 汇总对比) ==");
        SummaryStatistics s1 = new SummaryStatistics();
        for (double v : new double[] {2, 4, 6, 8, 10}) {
            s1.addValue(v);
        }
        SummaryStatistics s2 = new SummaryStatistics();
        for (double v : new double[] {1, 3, 5, 7, 9}) {
            s2.addValue(v);
        }
        SummaryStatistics all = new SummaryStatistics();
        for (double v : new double[] {2, 4, 6, 8, 10, 1, 3, 5, 7, 9}) {
            all.addValue(v);
        }
        System.out.printf("set1: N=%d mean=%.3f variance=%.3f sd=%.3f sum=%.1f%n",
            s1.getN(), s1.getMean(), s1.getVariance(), s1.getStandardDeviation(), s1.getSum());
        System.out.printf("set2: N=%d mean=%.3f variance=%.3f sd=%.3f sum=%.1f%n",
            s2.getN(), s2.getMean(), s2.getVariance(), s2.getStandardDeviation(), s2.getSum());
        System.out.printf("all : N=%d mean=%.3f variance=%.3f sd=%.3f sum=%.1f%n",
            all.getN(), all.getMean(), all.getVariance(), all.getStandardDeviation(), all.getSum());
        double weightedMean = (s1.getMean() * s1.getN() + s2.getMean() * s2.getN())
            / (s1.getN() + s2.getN());
        System.out.printf("加权平均核对: (mean1*N1 + mean2*N2)/(N1+N2) = %.3f(应等于 all.mean)%n",
            weightedMean);
    }

    static void linalg() {
        System.out.println("== 线性代数(Array2DRowRealMatrix / LU / Eigen) ==");
        Array2DRowRealMatrix a = new Array2DRowRealMatrix(new double[][] {{2, 3}, {1, 4}});
        Array2DRowRealMatrix b = new Array2DRowRealMatrix(new double[][] {{5, 6}, {7, 8}});
        System.out.println("A = [[2,3],[1,4]], B = [[5,6],[7,8]]");
        System.out.println("A.multiply(B) = " + a.multiply(b));

        LUDecomposition lu = new LUDecomposition(a);
        System.out.println("LUDecomposition(A).getDeterminant() = " + lu.getDeterminant());

        DecompositionSolver solver = lu.getSolver();
        double[] x = solver.solve(new ArrayRealVector(new double[] {5, 9})).toArray();
        System.out.println("solve A*x=[5,9] -> x = [" + x[0] + ", " + x[1] + "]");

        EigenDecomposition eig = new EigenDecomposition(a);
        double[] real = eig.getRealEigenvalues();
        double[] imag = eig.getImagEigenvalues();
        System.out.println("EigenDecomposition(A).getRealEigenvalues() = [" + real[0] + ", " + real[1] + "]");
        System.out.println("getImagEigenvalues() = [" + imag[0] + ", " + imag[1] + "]");
    }

    static void vector() {
        System.out.println("== RealVector(ArrayRealVector) ==");
        ArrayRealVector v1 = new ArrayRealVector(new double[] {1, 2, 3});
        ArrayRealVector v2 = new ArrayRealVector(new double[] {4, 5, 6});
        System.out.println("v1 = [1, 2, 3], v2 = [4, 5, 6]");
        System.out.println("v1.add(v2)          = " + v1.add(v2));
        System.out.println("v1.dotProduct(v2)   = " + v1.dotProduct(v2));
        System.out.println("v1.getNorm()        = " + v1.getNorm());
        System.out.println("v1.mapMultiply(10)  = " + v1.mapMultiply(10));
    }

    static void complex() {
        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());
    }

    static void primes() {
        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));
    }

    static void fraction() {
        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);
    }

    static void ttest() {
        System.out.println("== TTest(配对 t 检验 / 同方差 t 检验) ==");
        double[] before = {88.5, 90.2, 87.1, 91.4, 85.6, 89.8, 90.7, 86.9};
        double[] after  = {90.4, 91.9, 88.3, 93.0, 87.8, 91.5, 92.6, 88.1};
        TTest ttest = new TTest();
        System.out.println("pairedT(before, after)       = " + ttest.pairedT(before, after));
        System.out.println("pairedTTest p 值(双侧)        = " + ttest.pairedTTest(before, after));
        System.out.println("homoscedasticT(before, after) = " + ttest.homoscedasticT(before, after));
        System.out.println("homoscedasticTTest p 值(双侧) = " + ttest.homoscedasticTTest(before, after));
    }
}

说明:主类按组拆分方法,第四、五章逐组讲解;complex/primes/fraction 三组的独立拆解见第 4 篇《Apache Commons Math 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
java -Dfile.encoding=UTF-8 -cp target/classes:$M3 demo.MathDemo descriptive

输出:

EXIT=0
== DescriptiveStatistics(无界 + 滑动窗口) ==
data.length            = 20
getN()                 = 20
getMean()              = 7.0
getVariance()          = 9.368421052631579
getStandardDeviation() = 3.0607876523260447
getMax()               = 12.0
getMin()               = 2.0
getPercentile(50)      = 7.0
getSum()               = 140.0
-- 窗口大小 5 的滑动统计 --
add 1 -> N=1 mean=1.000 min=1 max=1
add 2 -> N=2 mean=1.500 min=1 max=2
add 3 -> N=3 mean=2.000 min=1 max=3
add 4 -> N=4 mean=2.500 min=1 max=4
add 5 -> N=5 mean=3.000 min=1 max=5
add 6 -> N=5 mean=4.000 min=2 max=6
add 7 -> N=5 mean=5.000 min=3 max=7
add 8 -> N=5 mean=6.000 min=4 max=8
after removeMostRecentValue: N=4 mean=5.5

验证点:EXIT=0 编译成功;descriptive 组完整跑通,后文 4.1/4.2 节基于此输出逐项解读。

四、统计 API 详解

统计能力集中在 org.apache.commons.math3.stat 包:descriptive 子包提供描述统计(两种门面:有存储的 DescriptiveStatistics 与无存储的 SummaryStatistics),inference 子包提供统计推断(t 检验、卡方检验等,本文实测 t 检验)。

4.1 DescriptiveStatistics:全量数据的描述统计

DescriptiveStatistics 内部保存全部历史值(无界模式),任何统计量都基于全量数据现算。4.1 节开头 descriptive() 的前半段就是它的标准用法:new DescriptiveStatistics() 无参构造后逐个 addValue,再调用各 getter。全部实测方法汇总:

方法实测返回含义
getN()20观测个数
getMean()7.0算术均值(总和 140 / 20)
getVariance()9.368421052631579样本方差(分母 N-1=19;140/19=7.368…? 实测 9.368…,见下方核对)
getStandardDeviation()3.0607876523260447样本标准差(方差开平方)
getMax()12.0最大值
getMin()2.0最小值
getPercentile(50)7.0中位数
getSum()140.0总和

核对:20 个数据求和 140(getSum 一致);偏差平方和 Σ(x-7)²=178,样本方差 178/19=9.368421052631579,与实测一致——注意 getVariance() 返回的是样本方差(除以 N-1),若需总体方差(除以 N,本例为 178/20=8.9)用 getPopulationVariance();标准差 3.0607876523260447 = 9.368421052631579 的平方根,一致。getPercentile(50) 用分位数插值算法求中位数,本例得 7.0。

4.2 DescriptiveStatistics:滑动窗口统计

new DescriptiveStatistics(5) 构造的是窗口上限为 5 的滑动统计:窗口满后再 addValue 会自动淘汰最旧值,N 恒为 5。4.1 节输出后半段逐行可见滚动过程(add 6 起 N 不再增长、min/max 开始右移),这就是流式监控「最近 N 个样本」的标准做法。

窗口相关还有一个易踩的 API 名差异——移除最近观测值的方法:

window.removeMostRecentValue();

实测结果(4.1 节输出末行):

after removeMostRecentValue: N=4 mean=5.5

移除 8 后剩 {4,5,6,7},均值 5.5 正确。math3 3.6.1 中该方法名是 removeMostRecentValue():写 removeMostRecentObservation() 编译报 cannot find symbol。实测用 javap 核对 math4-legacy 4.0-beta1 的同名类,其方法名同样是 removeMostRecentValue()——即两代在这个方法上同名,网传"math4 改名 removeMostRecentObservation"的说法与 4.0-beta1 的 jar 实测不符,写代码时以 javap 核对为准。

4.3 SummaryStatistics:常数内存的汇总统计

SummaryStatistics 不保存原始数据,只维护计数器与矩累加器,内存占用恒定,适合超大流量场景与多数据集汇总。summary() 组的实测:

java -Dfile.encoding=UTF-8 -cp target/classes:$M3 demo.MathDemo summary

输出:

== SummaryStatistics(两个数据集 + 汇总对比) ==
set1: N=5 mean=6.000 variance=10.000 sd=3.162 sum=30.0
set2: N=5 mean=5.000 variance=10.000 sd=3.162 sum=25.0
all : N=10 mean=5.500 variance=9.167 sd=3.028 sum=55.0
加权平均核对: (mean1*N1 + mean2*N2)/(N1+N2) = 5.500(应等于 all.mean)

解读:

  • set1={2,4,6,8,10} 均值 6、样本方差 10(Σ(x-6)²=40,40/4=10);set2 同理均值 5、方差 10。
  • 合并集 all 的方差 9.167 不是 10 的简单平均——方差不可直接加权合并,必须用全量数据重算(本例 all 是逐个 addValue 合并的,结果准确)。
  • 均值可加权合并:(6×5 + 5×5)/(5+5) = 5.5,与 all.mean 一致,这是分片统计(如各节点只报 N 与 mean)时合并均值的标准公式。

与 DescriptiveStatistics 的选型:需要分位数/最值回溯或滑动窗口选 DescriptiveStatistics(要存全量);只累积 N/mean/variance/sum 且数据量大选 SummaryStatistics(常数内存,但不支持 percentile)。

4.4 TTest:t 检验与 p 值解读

TTest 在 org.apache.commons.math3.stat.inference 包,无参构造后调用检验方法。ttest() 组用 8 组"处理前/处理后"配对数据实测两种检验:

java -Dfile.encoding=UTF-8 -cp target/classes:$M3 demo.MathDemo ttest

输出:

== TTest(配对 t 检验 / 同方差 t 检验) ==
pairedT(before, after)       = -13.717203674463095
pairedTTest p 值(双侧)        = 2.5795569554518058E-6
homoscedasticT(before, after) = -1.596337345388562
homoscedasticTTest p 值(双侧) = 0.13273255836476072

p 值解读(本例数据每对 after 均比 before 高约 1.9,8 组全部同向):

  • pairedTTest(配对 t 检验)p ≈ 2.58E-6:远小于常用显著性水平 0.05,结论是"处理后显著升高"。配对设计消除了个体间差异,只看每对的前后变化,8 组同向变化使 t 统计量高达 -13.72。
  • homoscedasticTTest(同方差独立样本 t 检验)p ≈ 0.133:大于 0.05,结论是"差异不显著"。因为它把 16 个值当作两组独立样本比较,组内个体差异(88~93 的波动)淹没了系统性抬升。
  • 两个 p 值相差 5 个数量级,检验方式的选择对结论影响巨大:同一批数据、同一水平 0.05,一个显著一个不显著。数据成对采集(同一对象前后对比)时必须用配对检验。

实测输出为科学计数法(2.5795569554518058E-6 即 0.00000258),直接与阈值比较无需格式化。

五、线性代数 API 详解

线性代数集中在 org.apache.commons.math3.linear 包。实矩阵门面是 RealMatrix 接口与默认实现 Array2DRowRealMatrix(行优先 double[][] 存储),分解器按算法分 LUDecomposition/EigenDecomposition 等类,统一通过 getSolver() 产出 DecompositionSolver 解方程。

5.1 Array2DRowRealMatrix 与矩阵乘法

linalg() 组前半段实测矩阵构造与乘法:

java -Dfile.encoding=UTF-8 -cp target/classes:$M3 demo.MathDemo linalg

输出:

== 线性代数(Array2DRowRealMatrix / LU / Eigen) ==
A = [[2,3],[1,4]], B = [[5,6],[7,8]]
A.multiply(B) = Array2DRowRealMatrix{{31.0,36.0},{33.0,38.0}}
LUDecomposition(A).getDeterminant() = 5.0
solve A*x=[5,9] -> x = [-1.4000000000000004, 2.6]
EigenDecomposition(A).getRealEigenvalues() = [0.9999999999999999, 5.0]
getImagEigenvalues() = [0.0, 0.0]

手算核对乘法:A·B 第 1 行 = {2×5+3×7, 2×6+3×8} = {31, 36},第 2 行 = {1×5+4×7, 1×6+4×8} = {33, 38},与实测完全一致。toString() 输出形如 Array2DRowRealMatrix{{31.0,36.0},{33.0,38.0}},调试时可直接打印。同组还实测了两个常用方法(见 4.4 节综合案例):transpose() 转置返回新矩阵、operate(RealVector) 矩阵乘向量返回 RealVector。

5.2 LUDecomposition:行列式与线性方程组

LU 分解是最通用的稠密矩阵分解,LUDecomposition(A) 后可取行列式与求解器:

  • getDeterminant():实测 A=[[2,3],[1,4]] 行列式 2×4-3×1=5.0,输出 5.0 一致。
  • getSolver() 返回 DecompositionSolver,solve(RealVector) 解 A·x=b。

实测解方程 A·x=[5,9]:

solve A*x=[5,9] -> x = [-1.4000000000000004, 2.6]

核对:A⁻¹ = (1/5)·[[4,-3],[-1,2]],A⁻¹·[5,9] = [(20-27)/5, (-5+18)/5] = [-1.4, 2.6],一致;-1.4000000000000004 末位的 000004 是 double 运算的正常浮点误差(见 5.5 节与第 4 篇第七章)。

API 签名注意:3.6.1 的 DecompositionSolver.solve() 只接受 RealVector/RealMatrix 参数,没有 solve(double[]) 重载。传数组编译报 cannot find symbol,必须先包装:solver.solve(new ArrayRealVector(new double[] {5, 9})).toArray()。

5.3 EigenDecomposition:特征值分解

EigenDecomposition(A) 分解后,getRealEigenvalues()/getImagEigenvalues() 分别返回实部与虚部特征值数组(实矩阵若有复特征值,两数组按位对应)。实测 A=[[2,3],[1,4]]:

EigenDecomposition(A).getRealEigenvalues() = [0.9999999999999999, 5.0]
getImagEigenvalues() = [0.0, 0.0]

核对:特征方程 λ²-6λ+5=0 → λ∈{1,5},实测 0.9999999999999999 与 5.0(1 的第一个位差是数值算法的正常舍入);虚部全 0 说明两个特征值均为实数。对称矩阵可改用 SymmetricEigenDecomposition 相关实现获得更高精度,本例用通用 EigenDecomposition 已足够。

5.4 RealVector:向量运算

vector() 组实测 ArrayRealVector 的常用方法:

java -Dfile.encoding=UTF-8 -cp target/classes:$M3 demo.MathDemo vector

输出:

== RealVector(ArrayRealVector) ==
v1 = [1, 2, 3], v2 = [4, 5, 6]
v1.add(v2)          = {5; 7; 9}
v1.dotProduct(v2)   = 32.0
v1.getNorm()        = 3.7416573867739413
v1.mapMultiply(10)  = {10; 20; 30}

逐项核对:add 逐元素相加 {5,7,9};dotProduct = 1×4+2×5+3×6 = 32;getNorm = √(1+4+9) = √14 ≈ 3.7416573867739413;mapMultiply 标量乘 {10,20,30}。注意 RealVector 的 toString() 用分号分隔({5; 7; 9}),与矩阵的花括号逗号格式不同。配合 mapMultiply/dotProduct 可直接实现投影、相似度(余弦)等常见运算,无需手写循环。

六、与 math4 的命名与 API 差异

3.6.1 与 math4/新资料混读时最容易踩的三处差异,均为实测锚点:

差异点math3 3.6.1 实测行为错误写法与实测报错正确写法
移除最近观测值removeMostRecentValue() 存在且可用removeMostRecentObservation() → 编译报 cannot find symbolwindow.removeMostRecentValue();
解方程入参solve() 只收 RealVector/RealMatrixsolver.solve(new double[] {5, 9}) → 编译报 cannot find symbolsolver.solve(new ArrayRealVector(new double[] {5, 9}))
分数约分/百分数Fraction 无 reduce()/percentageValue()f.reduce() → 编译报 cannot find symbol构造已自动约分(new Fraction(2,4) 即 1/2);百分数 f.doubleValue() * 100

补充两点包名层面的事实:math4 将旧 API 迁入 org.apache.commons.math4.legacy.*(DescriptiveStatistics 位于 commons-math4-legacy 模块,jar tf 实测类文件存在),FastMath 更名 AccurateMath 迁入 org.apache.commons.math4.legacy.core.jdkmath。两代包名不同,可同 classpath 共存(完整实测见第 1 篇安装指南 8.2 节)。

七、注意事项

  1. 方差语义:getVariance() 是样本方差(除以 N-1),总体方差用 getPopulationVariance();合并多个 SummaryStatistics 时方差不可加权平均,需全量重算。
  2. 检验方式决定结论:成对数据用 pairedTTest,独立样本才用 homoscedasticTTest/独立检验,选错结论可能反转(4.4 节实测 p 值相差 5 个数量级)。
  3. 浮点误差属正常:solve 结果 -1.4000000000000004、特征值 0.9999999999999999 都是 double 舍入,比较时用容差而非 ==。
  4. 复特征值:实矩阵可能产生复特征值,getRealEigenvalues()/getImagEigenvalues() 必须成对读取。
  5. 运行编码:POSIX/C locale 下加 -Dfile.encoding=UTF-8,否则中文输出变问号。
  6. 版本锚点:3.6.1 是断代约 10 年的最终版本,遇新资料 API 对不上时优先 javap 核对 jar 内真实方法签名。

八、常见问题(FAQ)

Q1:getPercentile(50) 和"平均数"有什么区别?为什么本例两者都是 7.0?

getPercentile(50) 是中位数(第 50 百分位数,用插值法计算),getMean() 是算术平均。20 个数据排序后中位落在 7 附近故为 7.0,均值恰也为 7.0(总和 140/20)。两者并不总是相等:数据偏斜时差异明显,如 4.3 节 all 集均值 5.5 而第 1 篇 MathCheck 输出里 10 个数的中位数为 7.5(排序后第 5、6 个数 7 与 8 的平均)。中位数抗离群值,均值受离群值牵引,按业务语义选择。

Q2:solve() 传 double 数组编译报错怎么办?

实测报错为 cannot find symbol: method solve(double[])。3.6.1 的 DecompositionSolver.solve() 只接受 RealVector/RealMatrix,先构造 new ArrayRealVector(new double[] {5, 9}) 再传入,结果用 .toArray() 取回 double 数组(见 5.2 节)。

Q3:同一个 classpath 里能同时用 math3 和 math4 吗?

能。两代顶层包名不同(org.apache.commons.math3 与 org.apache.commons.math4),不存在类冲突。完整实测(CoexistDemo 源码、编译运行命令、逐行回显)见第 1 篇《Ubuntu 22.04 安装 Apache Commons Math 指南》8.2 节:math3 与 math4-legacy 的 DescriptiveStatistics 同 classpath 各自正常工作。

Q4:为什么 pairedTTest 输出是 2.5795569554518058E-6 这种格式?

这是 double 的科学计数法字符串形式(2.58×10⁻⁶)。数值上直接与显著性水平比较即可(p < 0.05 成立);要展示时自行格式化,例如 String.format("%.2e", p) 输出 2.58e-06。p 值本身的业务解读见 4.4 节。

九、参考链接

  1. Apache Commons Math 官方站点:https://commons.apache.org/proper/commons-math/
  2. Commons Math 3.6.1 Javadoc:https://commons.apache.org/proper/commons-math/javadocs/api-3.6.1/
  3. 官方用户指南(统计与线性代数章节):https://commons.apache.org/proper/commons-math/userguide/stat.html
  4. Maven 中央仓库坐标页:https://repo1.maven.org/maven2/org/apache/commons/commons-math3/
  5. math4 迁移说明(RELEASE-NOTES 随二进制包提供):https://downloads.apache.org/commons/math/