Apache Commons Numbers 1.3 快速入门与集成示例

Apache Commons Numbers 1.3 快速入门,覆盖 Maven BOM 多模块集成、11 个模块逐一可运行的实测示例以及与 commons-math3 的选型对比。

Apache Commons NumbersApache Commons Numbers2026/10/15

Apache Commons Numbers 1.3 快速入门与集成示例

目标:10 分钟内完成 Apache Commons Numbers 1.3 的 Maven 集成并跑通第一个程序,随后对 11 个模块逐一给出可运行示例,全部输出已在 Ubuntu 22.04 实测通过。

一、环境信息

  • 操作系统:Ubuntu 22.04.5 LTS(x86_64)
  • 架构:amd64
  • JDK:OpenJDK 17.0.20.1(openjdk-17-jdk 17.0.20.1+1-1~22.04)
  • 构建工具:Apache Maven 3.6.3
  • 组件版本:Apache Commons Numbers 1.3(2026 年 4 月发布,要求 Java 8 及以上)
  • Maven 坐标:org.apache.commons:commons-numbers-bom:1.3(BOM)统一管理 11 个运行时模块:commons-numbers-core、commons-numbers-angle、commons-numbers-arrays、commons-numbers-combinatorics、commons-numbers-complex、commons-numbers-field、commons-numbers-fraction、commons-numbers-gamma、commons-numbers-primes、commons-numbers-quaternion、commons-numbers-rootfinder
  • 工作目录:/root/numbers-demo

二、10 分钟最小可用

2.1 集成方式选择

Numbers 是多模块项目,官方提供了 commons-numbers-bom 依赖清单(BOM)做版本对齐:工程引入 BOM 后,各模块依赖不再写版本号,11 个模块永远同版本协同。首次接触建议一次引入全部 11 个模块跑通示例,正式项目再按需裁剪。

2.2 创建工程并引入依赖

mkdir -p /root/numbers-demo/src/main/java/demo
cd /root/numbers-demo

创建 pom.xml,通过 dependencyManagement 导入 BOM 并声明 11 个模块依赖:

<?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>numbers-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>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-numbers-bom</artifactId>
        <version>1.3</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-numbers-core</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-numbers-angle</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-numbers-arrays</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-numbers-combinatorics</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-numbers-complex</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-numbers-field</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-numbers-fraction</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-numbers-gamma</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-numbers-primes</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-numbers-quaternion</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-numbers-rootfinder</artifactId>
    </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>

用 dependency:tree 核对 BOM 是否把 11 个模块全部解析到 1.3:

mvn -B dependency:tree

输出节选(实测):

[INFO] demo:numbers-demo:jar:1.0.0
[INFO] +- org.apache.commons:commons-numbers-core:jar:1.3:compile
[INFO] +- org.apache.commons:commons-numbers-angle:jar:1.3:compile
[INFO] +- org.apache.commons:commons-numbers-arrays:jar:1.3:compile
[INFO] +- org.apache.commons:commons-numbers-combinatorics:jar:1.3:compile
[INFO] +- org.apache.commons:commons-numbers-complex:jar:1.3:compile
[INFO] +- org.apache.commons:commons-numbers-field:jar:1.3:compile
[INFO] +- org.apache.commons:commons-numbers-fraction:jar:1.3:compile
[INFO] +- org.apache.commons:commons-numbers-gamma:jar:1.3:compile
[INFO] +- org.apache.commons:commons-numbers-primes:jar:1.3:compile
[INFO] +- org.apache.commons:commons-numbers-quaternion:jar:1.3:compile
[INFO] \- org.apache.commons:commons-numbers-rootfinder:jar:1.3:compile
[INFO] BUILD SUCCESS

验证结果:11 个模块全部解析为 1.3,无版本漂移。

2.3 编写第一个程序

写一个覆盖三个高频模块(core、fraction、angle)的入门程序 src/main/java/demo/FirstDemo.java:

package demo;

import org.apache.commons.numbers.angle.Angle;
import org.apache.commons.numbers.core.ArithmeticUtils;
import org.apache.commons.numbers.core.Precision;
import org.apache.commons.numbers.fraction.Fraction;

public class FirstDemo {
    public static void main(String[] args) {
        // core:整数运算与浮点比较
        System.out.println("gcd(48, 18)              = " + ArithmeticUtils.gcd(48, 18));
        System.out.println("pow(3, 39)               = " + ArithmeticUtils.pow(3L, 39));
        System.out.println("equalsIncludingNaN(NaN, NaN) = "
                + Precision.equalsIncludingNaN(Double.NaN, Double.NaN));
        // fraction:构造即约分,运算返回新对象
        Fraction f1 = Fraction.of(1, 3);
        Fraction f2 = Fraction.of(1, 6);
        System.out.println("Fraction.of(1,3).add(Fraction.of(1,6)) = " + f1.add(f2));
        // angle:三种角度单位互转
        System.out.println("Deg.of(90).toRad().getAsDouble() = "
                + Angle.Deg.of(90).toRad().getAsDouble());
    }
}

2.4 编译与运行

mvn -B clean compile

输出节选(实测首尾):

[INFO] Scanning for projects...
[INFO] ------------------< demo:numbers-demo >------------------
[INFO] Building numbers-demo 1.0.0
[INFO] --- maven-compiler-plugin:3.11.0:compile (default-compile) @ numbers-demo ---
[INFO] Compiling 1 source file with javac [debug release 17] to target/classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  13.745 s
[INFO] Finished at: 2026-10-15T01:20:12Z
[INFO] ------------------------------------------------------------------------

编译成功后生成运行所需的 classpath 文件,再直接用 java 运行:

mvn -q -B dependency:build-classpath -Dmdep.outputFile=cp.txt
java -Dfile.encoding=UTF-8 -cp 'target/classes:'"$(cat cp.txt)" demo.FirstDemo

实测回显:

gcd(48, 18)              = 6
pow(3, 39)               = 4052555153018976267
equalsIncludingNaN(NaN, NaN) = true
Fraction.of(1,3).add(Fraction.of(1,6)) = 1 / 2
Deg.of(90).toRad().getAsDouble() = 1.5707963267948966

验证结果:三个模块 API 全部调用成功;pow(3L, 39) 得到 3.4e18 量级的长整型精确值,未溢出(pow(3L, 40) 会抛 ArithmeticException: long overflow,这正是 Numbers 的溢出即报错设计,详见第五章)。

2.5 验证点小结

验证点预期实测
BOM 解析11 个模块统一 1.3dependency:tree 全部 jar:1.3:compile
编译BUILD SUCCESSCompiling 1 source file,Total time 13.745 s
gcd66
分数加法1/21 / 2
度转弧度1.57079632679489661.5707963267948966

三、全模块功能速览

以下示例程序 demo.NumbersDemo 按 11 个模块分组,main 方法接收组名参数,逐组实测运行。完整源码见本章各代码块(每组给出核心调用语句),输出为该组完整实测回显,运行命令统一为:

cd /root/numbers-demo
mvn -q -B dependency:build-classpath -Dmdep.outputFile=cp.txt
CP='target/classes:'"$(cat cp.txt)"
java -Dfile.encoding=UTF-8 -cp "$CP" demo.NumbersDemo 组名

3.1 core:算术工具与精度控制

System.out.println("gcd(48, 18)              = " + ArithmeticUtils.gcd(48, 18));
System.out.println("lcm(4, 6)                = " + ArithmeticUtils.lcm(4, 6));
try {
    System.out.println("pow(3, 40)               = " + ArithmeticUtils.pow(3L, 40));
} catch (ArithmeticException e) {
    System.out.println("pow(3, 40)               = ArithmeticException: " + e.getMessage());
}
System.out.println("equalsIncludingNaN(NaN, NaN) = "
        + Precision.equalsIncludingNaN(Double.NaN, Double.NaN));
Sum s = Sum.create();
for (int i = 0; i < 10; i++) {
    s.add(0.1);
}
System.out.println("Sum.add(0.1) x 10   = " + s.getAsDouble());
System.out.println("double: 1e16 + 1        = " + (1e16 + 1));
System.out.println("DD: 1e16 + 1            = " + DD.of(1e16).add(DD.of(1)).bigDecimalValue());

实测运行 java -cp "$CP" demo.NumbersDemo core:

== core: ArithmeticUtils ==
gcd(48, 18)              = 6
gcd(-48, 18)             = 6
lcm(4, 6)                = 12
pow(2, 10)               = 1024
pow(3, 39)               = 4052555153018976267
pow(3, 40)               = ArithmeticException: long overflow
isPowerOfTwo(1024)       = true
isPowerOfTwo(1000)       = false
divideUnsigned(-1, 4)    = 1073741823
remainderUnsigned(-1, 4) = 3
== core: Precision ==
equals(1.1*3, 3.3)                    = true
equals(1.1*3, 3.3, 1 ulp)             = true
equals(NaN, NaN)                      = false
equalsIncludingNaN(NaN, NaN)          = true
equalsIncludingNaN(0.0, -0.0)         = true
equalsWithRelativeTolerance(100.0, 100.00001, 1e-6) = true
compareTo(3.3, 3.3000000000000003, 1 ulp) = 0
round(3.14159, 2)                     = 3.14
round(2.675, 2)                       = 2.68
round(2.675, 2, HALF_UP)              = 2.68
EPSILON                               = 1.1102230246251565E-16
== core: Sum 扩展精度求和 ==
naive 0.1 x 10      = 0.9999999999999999
Sum.add(0.1) x 10   = 1.0
naive dot(a, b)     = 0.0
Sum.ofProducts(a,b) = 2.0
== core: DD 双双精度 ==
double: 1e16 + 1        = 1.0E16
DD: 1e16 + 1            = 10000000000000001
DD hi/lo                = 1.0E16 / 1.0
DD sqrt(2).doubleValue  = 1.4142135623730951

要点提示:朴素累加 10 次 0.1 得 0.9999999999999999,而 core 的 Sum 补偿求和得 1.0;DD 双双精度把 1e16 + 1 的 1 保住了,double 类型则直接丢失。

3.2 angle:三种角度单位与归一化

Angle.Rad halfPi = Angle.Rad.of(Math.PI / 2);
System.out.println("Rad.of(pi/2).toDeg().getAsDouble() = " + halfPi.toDeg().getAsDouble());
System.out.println("Deg.of(90).toTurn().getAsDouble()  = "
        + Angle.Deg.of(90).toTurn().getAsDouble());
DoubleUnaryOperator n360 = Angle.Deg.normalizer(0);
System.out.println("Deg.normalizer(0).apply(450)       = " + n360.applyAsDouble(450));
Reduce reduce = new Reduce(0.0, 2 * Math.PI);
System.out.println("new Reduce(0, 2pi).apply(3.5*pi)   = " + reduce.applyAsDouble(3.5 * Math.PI));
System.out.println("CosAngle.value([1,2,3], [2,4,6])   = "
        + CosAngle.value(new double[]{1, 2, 3}, new double[]{2, 4, 6}));

实测回显:

Rad.of(pi/2).toDeg().getAsDouble() = 90.0
Deg.of(90).toRad().getAsDouble()   = 1.5707963267948966
Deg.of(90).toTurn().getAsDouble()  = 0.25
Rad.of(3.5*pi).toDeg().getAsDouble() = 630.0
Deg.normalizer(0).apply(450)       = 90.0
Rad.normalizer(0).apply(3.5*pi)    = 4.71238898038469
new Reduce(0, 2pi).apply(3.5*pi)   = 4.71238898038469
CosAngle.value([1,2,3], [2,4,6])   = 1.0
CosAngle.value([1,0], [0,1])       = 0.0

要点提示:Angle.Deg.normalizer(lowerBound) 的参数是归一化区间下界,把任意角度规约到 [下界, 下界 + 360);Reduce(period, lower) 构造参数先是区间下界再是周期,顺序与直觉相反,传反会得到 NaN。

3.3 arrays:顺序统计量与多维索引

double[] data = {9, 3, 7, 1, 8, 2, 6, 5, 4, 0};
double[] copy = data.clone();
Selection.select(copy, 3);
System.out.println("after select(copy, 3): " + Arrays.toString(copy));
double[] keys = {3.0, 1.0, 2.0};
double[] vals = {30.0, 10.0, 20.0};
SortInPlace.ASCENDING.apply(keys, vals);
MultidimensionalCounter counter = MultidimensionalCounter.of(2, 3, 4);
System.out.println("toUni(1, 2, 3)      = " + counter.toUni(1, 2, 3));
System.out.println("toMulti(23)         = " + Arrays.toString(counter.toMulti(23)));

实测回显:

before: [9.0, 3.0, 7.0, 1.0, 8.0, 2.0, 6.0, 5.0, 4.0, 0.0]
after select(copy, 3): [0.0, 1.0, 2.0, 3.0, 8.0, 4.0, 6.0, 5.0, 7.0, 9.0]
a[3] (第4小的数) = 3.0
after select(copy, [0, 9]): [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
keys   = [1.0, 2.0, 3.0]
values = [10.0, 20.0, 30.0]
getSize()           = 24
getSizes()          = [2, 3, 4]
toUni(1, 2, 3)      = 23
toMulti(23)         = [1, 2, 3]
toMulti(0)          = [0, 0, 0]

要点提示:Selection.select 原地部分排序,只保证第 k 位是第 k 小、两侧分别不大于与不小于它;同时传 {0, 9} 两个关键索引后整个数组升序就位。注意 1.3 的 arrays 模块没有旧版的 DoubleArray 类(1.0 稳定版起已移除),数组封装请用 JDK 自带结构。

3.4 fraction:分数精确运算与连分数

Fraction f1 = Fraction.of(1, 3);
Fraction f2 = Fraction.of(1, 6);
System.out.println("f1.add(f2)        = " + f1.add(f2));
System.out.println("Fraction.of(2, 4) = " + Fraction.of(2, 4));
System.out.println("Fraction.of(-3, -6) = " + Fraction.of(-3, -6));
System.out.println("Fraction.from(3.14159, 100) = " + Fraction.from(3.14159, 100));
try {
    Fraction.of(1, 0);
} catch (RuntimeException e) {
    System.out.println("Fraction.of(1, 0)   = " + e.getClass().getSimpleName() + ": " + e.getMessage());
}
System.out.println("BigFraction.of(123456789012345, 987654321098765) = "
        + BigFraction.of(123456789012345L, 987654321098765L));

实测回显:

f1 = 1 / 3, f2 = 1 / 6
f1.add(f2)        = 1 / 2
f1.subtract(f2)   = 1 / 6
f1.multiply(f2)   = 1 / 18
f1.divide(f2)     = 2
f1.pow(2)         = 1 / 9
f1.reciprocal()   = 3
f1.abs()          = 1 / 3
Fraction.of(2, 4) = 1 / 2 (numerator=1, denominator=2)
Fraction.of(-3, -6) = -1 / -2
Fraction.of(6, 3)   = 2
f1.doubleValue()    = 0.3333333333333333
Fraction.parse("3/4") = 3 / 4
Fraction.from(0.5)  = 1 / 2
Fraction.from(3.14159, 100) = 22 / 7
Fraction.of(1, 0)   = FractionException: Denominator must be different from 0
compareTo: (-1/3) vs (-1/2) = 1
ContinuedFraction 求自然常数 e = 2.7182818284590455(与 Math.E 差 4.440892098500626E-16)
BigFraction.of(123456789012345, 987654321098765) = 24691357802469 / 197530864219753
BigFraction.from(pi, 100) = 22 / 7
BigFraction.from(pi, 100).doubleValue()  = 3.142857142857143

要点提示:Fraction.of(-3, -6) 输出 -1 / -2——1.3 只按 gcd 约分、不做符号归一,这是与 commons-math3 的显著差异;Fraction.from(3.14159, 100) 给出分母不超过 100 的最佳逼近 22/7。

3.5 primes:素数判定与因子分解

System.out.println("isPrime(97)         = " + Primes.isPrime(97));
System.out.println("isPrime(2147483647) = " + Primes.isPrime(2147483647));
System.out.println("nextPrime(100)      = " + Primes.nextPrime(100));
System.out.println("primeFactors(360)   = " + Primes.primeFactors(360));

实测回显:

isPrime(2)          = true
isPrime(97)         = true
isPrime(1)          = false
isPrime(100)        = false
isPrime(2147483647) = true
nextPrime(97)       = 97
nextPrime(100)      = 101
nextPrime(1)        = 2
primeFactors(360)   = [2, 2, 2, 3, 3, 5]
primeFactors(97)    = [97]
primeFactors(1024)  = [2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
primeFactors(5460)  = [2, 2, 3, 5, 7, 13]

要点提示:isPrime 基于 Miller-Rabin 与小素数试除的组合,2147483647(2^31 - 1,梅森素数)瞬间判定为 true;nextPrime 保证返回不小于入参的最小素数。

3.6 complex:复数运算

Complex a = Complex.ofCartesian(3, 4);
Complex b = Complex.ofCartesian(1, -2);
System.out.println("a.multiply(b) = " + a.multiply(b));
System.out.println("a.abs()       = " + a.abs());
System.out.println("a.pow(2)      = " + a.pow(2));
System.out.println("I.multiply(I) = " + Complex.I.multiply(Complex.I));
System.out.println("a.sqrt()      = " + a.sqrt());

实测回显:

a = (3.0,4.0), b = (1.0,-2.0)
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.arg()       = 0.9272952180016122
a.conj()      = (3.0,-4.0)
a.pow(2)      = (-6.999999999999997,24.0)
a.sqrt()      = (2.0,1.0)
I.multiply(I) = (-1.0,0.0)
ofPolar(1, pi/2) = (6.123233995736766E-17,1.0)
ofCis(pi)        = (-1.0,1.2246467991473532E-16)
a.exp()          = (-13.128783081462158,-15.200784463067954)
a.nthRoot(3)     = [(1.6289371459221758,0.5201745023045458), (-1.2649529063577512,1.1506136983844508), (-0.36398423956442444,-1.6707882006889963)]

要点提示:复数构造用静态工厂 ofCartesian/ofPolar/ofCis,输出格式为 (实部,虚部);共轭方法名是 conj() 而非 conjugate,虚部常量是 Complex.I。

3.7 quaternion:四元数运算

System.out.println("I.multiply(J) = " + Quaternion.multiply(Quaternion.I, Quaternion.J));
Quaternion q = Quaternion.of(0.7071067811865476, 0.7071067811865476, 0.0, 0.0);
System.out.println("q.norm()      = " + q.norm());
Quaternion p = Quaternion.of(1.0, new double[]{2.0, 3.0, 4.0});
Slerp slerp = new Slerp(q, Quaternion.of(0.0, 0.0, 0.0, 1.0));
System.out.println("slerp(0.5)    = " + slerp.apply(0.5));

实测回显:

I.multiply(J) = [0.0 0.0 0.0 1.0]
J.multiply(K) = [0.0 1.0 0.0 0.0]
K.multiply(I) = [0.0 0.0 1.0 0.0]
q             = [0.7071067811865476 0.7071067811865476 0.0 0.0]
q.norm()      = 1.0
q.isUnit(1e-10) = true
q.conjugate() = [0.7071067811865476 -0.7071067811865476 -0.0 -0.0]
q.inverse()   = [0.7071067811865475 -0.7071067811865475 -0.0 -0.0]
p = of(1, [2,3,4])  = [1.0 2.0 3.0 4.0]
p.getScalarPart()   = 1.0
p.getVectorPart()   = [2.0, 3.0, 4.0]
p.normSq()          = 30.0
slerp(0.5)    = [0.5000000000000001 0.5000000000000001 0.0 0.7071067811865476]
slerp(0.0)    = [0.7071067811865476 0.7071067811865476 0.0 0.0]
slerp(1.0)    = [0.0 0.0 0.0 1.0]

要点提示:Hamilton 乘法 i×j=k 在 1.3 中既可用静态 Quaternion.multiply(I, J) 也可用实例方法 I.multiply(J),两者结果一致(实测均为 [0.0 0.0 0.0 1.0]);Slerp 构造后按参数 t 插值,t=0、t=1 精确回到两端点。另注意 quaternion 模块的 isUnit 等方法在运行期会用到 core 的 Precision 类,Maven 下由传递依赖自动带入,jar 直跑时需把 commons-numbers-core-1.3.jar 一并加入 classpath。

3.8 gamma:特殊函数

System.out.println("Gamma.value(5.0)      = " + Gamma.value(5.0));
System.out.println("LogGamma.value(100.0) = " + LogGamma.value(100.0));
System.out.println("Digamma.value(1.0)    = " + Digamma.value(1.0));
System.out.println("Erf.value(1.0)        = " + Erf.value(1.0));
System.out.println("Beta.value(2, 3)      = " + Beta.value(2, 3));

实测回显:

Gamma.value(5.0)        = 24.0
Gamma.value(0.5)        = 1.772453850905516
Gamma.value(100.0)      = 9.332621544394415E155
LogGamma.value(100.0)   = 359.1342053695754
Math.log(Gamma.value(100)) 核对 = 359.1342053695754
Digamma.value(1.0)      = -0.5772156649015314
Trigamma.value(1.0)     = 1.644934066848156
Erf.value(1.0)          = 0.8427007929497149
Erfc.value(1.0)         = 0.15729920705028513
InverseErfc.value(Erfc.value(1.0)) = 1.0
Beta.value(2, 3)        = 0.0833333333333333
LogBeta.value(2, 3)     = -2.4849066497880004

要点提示:Gamma.value(5.0) 即 4! = 24;Digamma.value(1.0) 等于负欧拉常数 -0.5772156649;LogGamma 与对 Gamma 取对数的核对值逐位一致。

3.9 combinatorics:阶乘、二项式系数与组合枚举

System.out.println("Factorial.value(20)        = " + Factorial.value(20));
System.out.println("Factorial.doubleValue(170) = " + Factorial.doubleValue(170));
System.out.println("BinomialCoefficient.value(52, 5) = " + BinomialCoefficient.value(52, 5));
System.out.println("Stirling.stirlingS2(5, 2)  = " + Stirling.stirlingS2(5, 2));
Combinations c = Combinations.of(5, 2);
int count = 0;
for (int[] combo : c) {
    count++;
}
System.out.println("组合总数      = " + count);

实测回显:

Factorial.value(20)          = 2432902008176640000
Factorial.doubleValue(170)   = 7.257415615307999E306
Factorial.value(21)          = CombinatoricsException: Number 21 is out of range [0, 20]
BinomialCoefficient.value(52, 5) = 2598960
BinomialCoefficient.value(66, 33) = 7219428434016265740
BinomialCoefficient.value(67, 33) = ArithmeticException: long overflow
BinomialCoefficientDouble.value(67, 33) = 1.4226520737620287E19
LogBinomialCoefficient.value(1000, 500) = 689.4672615678512
LogFactorial.create().value(1000)       = 5912.128178488163
Stirling.stirlingS2(5, 2)    = 15
Stirling.stirlingS1(4, 2)    = 11
Combinations.of(5, 2): n=5 k=2
前 5 组合: [0, 1] [0, 2] [1, 2] [0, 3] [1, 3]
组合总数      = 10(= C(5,2) = 10)

要点提示:Factorial.value 精确范围到 20(long 上限),超出抛异常;BinomialCoefficient.value(67, 33) 溢出抛 ArithmeticException: long overflow,需要大数时换 BinomialCoefficientDouble 或 LogBinomialCoefficient。

3.10 field:可插拔域代数

FP64 x = FP64.of(6);
FP64 y = FP64.of(4);
System.out.println("x.add(y)        = " + x.add(y));
System.out.println("x.reciprocal()  = " + x.reciprocal());
FP64Field f = FP64Field.get();
System.out.println("FP64Field.get().one() = " + f.one() + ", zero() = " + f.zero());
System.out.println("FractionField.get().one() = " + FractionField.get().one());

实测回显:

x = 6.0, y = 4.0
x.add(y)        = 10.0
x.subtract(y)   = 2.0
x.multiply(y)   = 24.0
x.divide(y)     = 1.5
x.reciprocal()  = 0.16666666666666666
x.pow(2)        = 36.0
x.isZero()/isOne() = false / false
FP64Field.get().one() = 1.0, zero() = 0.0
FractionField.get().one() = 1

要点提示:FP64 把 double 包装成满足域公理的对象,配合 FP64Field 单例可写出与具体数值类型无关的泛型算法;FractionField.get().one() 返回分数 1/1(toString 输出为 1)。

3.11 rootfinder:Brent 法求根

BrentSolver solver = new BrentSolver(1e-12, 1e-12, 1e-12);
DoubleUnaryOperator f1 = x -> x * x - 2;
double r1 = solver.findRoot(f1, 0.0, 2.0);
System.out.println("root of x^2 - 2 in [0, 2]  = " + r1);

实测回显:

root of x^2 - 2 in [0, 2]  = 1.4142135623731364
Math.sqrt(2) 核对          = 1.4142135623730951
f1(root)                   = 1.1723955140041653E-13
root of cos(x) in [0, 2]   = 1.5707963267948966
cos(root) 核对             = 6.123233995736766E-17

要点提示:findRoot 要求区间两端函数值异号,否则抛 SolverException;结果精度由构造参数(相对误差、绝对误差、函数值误差)控制,根处函数值 1.17e-13 与 1e-12 的函数值精度设置一致。

四、Maven 集成常见坑

  1. BOM import 后仍写版本号不报错但会覆盖 BOM。工程内模块依赖一律省略 <version>,版本只保留在 BOM 一处。
  2. mvn -q package 静默模式成功时无输出。判断成功看退出码或去掉 -q,不要等日志。
  3. 运行中文输出乱码:apt/CentOS 默认 locale 为 POSIX,java 运行加 -Dfile.encoding=UTF-8 -Dstdout.encoding=UTF-8。
  4. 模块版本漂移:绕过 BOM 单独指定某模块版本(如 core 用 1.2 其余 1.3)不会被拦截,运行期可能出现 NoSuchMethodError,务必保持同版本。
  5. 传递依赖很薄:11 个模块中仅 fraction、angle、complex、quaternion、field 等传递依赖 core,gamma、combinatorics、arrays、primes、rootfinder 连 core 都不依赖,裁剪空间大。

五、与 commons-math3 的关系与共存

维度Commons Numbers 1.3Commons Math3 3.6.1
工程形态11 个独立小模块单个 fat jar(约 2 MB)
分数 APIFraction.of(1, 3) 静态工厂,不可变new Fraction(1, 3) 构造器
分数符号-3/-6 仅约分不翻符号(输出 -1 / -2)构造即符号归一(输出 1/2)
复数 APIComplex.ofCartesian(3, 4)、conj()、arg()new Complex(3, 4)、conjugate()、getArgument()
素数 APIPrimes.isPrime(含 Miller-Rabin)Primes.isPrime(试除)
精度求和core 提供 Sum 与 DD 双双精度无对应能力
维护状态活跃维护(2026 年 4 月发布 1.3)停留在 3.6.1,仅安全修复

两库可以在同一工程共存:artifactId 完全不同(commons-numbers-* 与 commons-math3),无类路径冲突。建议:只用到数值基础能力的新项目选 Numbers;依赖 math3 独有的优化、拟合、ODE 等功能时再引入 math3。

六、选型建议

  • 只需浮点比较、gcd、扩展精度求和:仅引 commons-numbers-core,约 100 KB。
  • 需要分数精确记账(比例、汇率类计算避免浮点误差):补 commons-numbers-fraction,金额大的场景用 BigFraction。
  • 信号处理、图形学:complex、quaternion 组合。
  • 统计与概率计算:gamma(LogGamma、Erf)与 combinatorics(对数空间组合数)组合,避免溢出。
  • 排序与选择:arrays 的 Selection 是内省式快速选择,适合求 top-k 而不必全排序。
  • 求非线性方程根:rootfinder 的 BrentSolver,区间只需异号即可。

七、参考链接