Apache Commons Numbers 1.3 快速入门与集成示例
Apache Commons Numbers 1.3 快速入门,覆盖 Maven BOM 多模块集成、11 个模块逐一可运行的实测示例以及与 commons-math3 的选型对比。
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.3 | dependency:tree 全部 jar:1.3:compile |
| 编译 | BUILD SUCCESS | Compiling 1 source file,Total time 13.745 s |
| gcd | 6 | 6 |
| 分数加法 | 1/2 | 1 / 2 |
| 度转弧度 | 1.5707963267948966 | 1.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 集成常见坑
- BOM import 后仍写版本号不报错但会覆盖 BOM。工程内模块依赖一律省略
<version>,版本只保留在 BOM 一处。 mvn -q package静默模式成功时无输出。判断成功看退出码或去掉-q,不要等日志。- 运行中文输出乱码:apt/CentOS 默认 locale 为 POSIX,java 运行加
-Dfile.encoding=UTF-8 -Dstdout.encoding=UTF-8。 - 模块版本漂移:绕过 BOM 单独指定某模块版本(如 core 用 1.2 其余 1.3)不会被拦截,运行期可能出现 NoSuchMethodError,务必保持同版本。
- 传递依赖很薄:11 个模块中仅 fraction、angle、complex、quaternion、field 等传递依赖 core,gamma、combinatorics、arrays、primes、rootfinder 连 core 都不依赖,裁剪空间大。
五、与 commons-math3 的关系与共存
| 维度 | Commons Numbers 1.3 | Commons Math3 3.6.1 |
|---|---|---|
| 工程形态 | 11 个独立小模块 | 单个 fat jar(约 2 MB) |
| 分数 API | Fraction.of(1, 3) 静态工厂,不可变 | new Fraction(1, 3) 构造器 |
| 分数符号 | -3/-6 仅约分不翻符号(输出 -1 / -2) | 构造即符号归一(输出 1/2) |
| 复数 API | Complex.ofCartesian(3, 4)、conj()、arg() | new Complex(3, 4)、conjugate()、getArgument() |
| 素数 API | Primes.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,区间只需异号即可。
七、参考链接
- Apache Commons Numbers 官方主页:https://commons.apache.org/proper/commons-numbers/
- 1.3 javadoc:https://commons.apache.org/proper/commons-numbers/javadocs/api-release/index.html
- 官方下载目录:https://downloads.apache.org/commons/numbers/
- Maven 中央仓库 BOM:https://repo1.maven.org/maven2/org/apache/commons/commons-numbers-bom/
- 发行说明:https://commons.apache.org/proper/commons-numbers/changes-report.html
- 源码仓库:https://github.com/apache/commons-numbers