Apache Commons Numbers 1.3 常用 API 与核心模块详解

Apache Commons Numbers 1.3 常用 API 与核心模块详解,按 11 个模块族逐章拆解 Precision、Fraction、Complex、Quaternion 等核心类的用法并附实测回显。

Apache Commons NumbersApache Commons Numbers2026/10/15

Apache Commons Numbers 1.3 常用 API 与核心模块详解

目标:按模块族拆解 Apache Commons Numbers 1.3 的 11 个运行时模块,逐章给出核心类的 API 签名、调用示例与实测回显,全部示例已在 Ubuntu 22.04 的 OpenJDK 17 环境编译运行通过。

一、环境信息

  • 操作系统:Ubuntu 22.04.5 LTS(x86_64)
  • 架构:amd64
  • JDK:OpenJDK 17.0.20.1
  • 构建工具:Apache Maven 3.6.3
  • 组件版本:Apache Commons Numbers 1.3(要求 Java 8 及以上)
  • Maven 坐标:org.apache.commons:commons-numbers-bom:1.3(BOM)统一管理 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 共 11 个运行时模块
  • 工作目录:/root/numbers-demo

二、模块与 API 总览

Numbers 1.3 的设计基调是:不可变值对象、静态工厂构造、溢出即抛异常、单模块零额外依赖。11 个模块与核心类总览:

模块 artifactId核心类一句话职责
commons-numbers-corePrecision、ArithmeticUtils、Sum、DD浮点比较、整数运算与溢出、补偿求和、双双精度
commons-numbers-angleAngle(嵌套 Rad/Deg/Turn)、Reduce、CosAngle角度单位与归一化、向量夹角
commons-numbers-arraysSelection、SortInPlace、MultidimensionalCounter顺序统计量、键值同步排序、多维索引换算
commons-numbers-combinatoricsFactorial、BinomialCoefficient、LogFactorial、Stirling、Combinations阶乘与组合数学
commons-numbers-complexComplex不可变复数
commons-numbers-fieldField、FP64、FP64Field、FractionField域代数抽象与泛型算法
commons-numbers-fractionFraction、BigFraction、ContinuedFraction、GeneralizedContinuedFraction分数与连分数
commons-numbers-gammaGamma、LogGamma、Digamma、Trigamma、Erf、Erfc、Beta特殊函数族
commons-numbers-primesPrimes素数判定、生成、分解
commons-numbers-quaternionQuaternion、Slerp四元数与球面插值
commons-numbers-rootfinderBrentSolverBrent 法非线性求根

依赖关系极简:primes、arrays、combinatorics、gamma、rootfinder 完全不依赖其他模块;fraction、angle、complex、quaternion、field 依赖 core(Maven 下自动传递)。本章示例工程同时引入全部 11 个模块,用 BOM 锁定 1.3。

三、core 模块:精度控制与整数运算

3.1 Precision:浮点比较与舍入

浮点数不能直接用 == 比较,Precision 提供三套语义不同的比较策略:

策略方法适用场景
ULP 位比较equals(a, b, maxUlps)通用默认,与数值大小无关,二进制位级容差
绝对误差equals(a, b, epsilon)数值范围已知且固定(如 [0,1] 的概率)
相对误差equalsWithRelativeTolerance(a, b, tol)数值跨数量级(如 1e-3 到 1e6 的物理量)

💡 技术拓展:浮点比较策略对比——绝对误差在数值大时形同虚设(1e10 与 1e10+0.001 的绝对误差很小但相对误差更小),相对误差在接近 0 时失效(0 与 1e-300 的相对误差无穷大),ULP 比较则在 0 与 -0.0、NaN 附近各有特判。本文示例统一使用 ULP 比较为主、绝对误差为辅的组合,NaN 处理用 equalsIncludingNaN 系列。

import org.apache.commons.numbers.core.Precision;

// ULP 比较:1.1 * 3 与 3.3 相差 1 个 ULP
System.out.println("equals(1.1*3, 3.3) = " + Precision.equals(1.1 * 3, 3.3));
System.out.println("equals(1.1*3, 3.3, 1 ulp) = " + Precision.equals(1.1 * 3, 3.3, 1));
// NaN 与正负零的语义差
System.out.println("equals(NaN, NaN) = " + Precision.equals(Double.NaN, Double.NaN));
System.out.println("equalsIncludingNaN(NaN, NaN) = " + Precision.equalsIncludingNaN(Double.NaN, Double.NaN));
System.out.println("equalsIncludingNaN(0.0, -0.0) = " + Precision.equalsIncludingNaN(0.0, -0.0));
// 相对误差与带容差排序
System.out.println("equalsWithRelativeTolerance(100.0, 100.00001, 1e-6) = "
        + Precision.equalsWithRelativeTolerance(100.0, 100.00001, 1e-6));
System.out.println("compareTo(3.3, 3.3000000000000003, 1 ulp) = "
        + Precision.compareTo(3.3, 3.3000000000000003, 1));
// 舍入与对象化等价器
System.out.println("round(2.675, 2) = " + Precision.round(2.675, 2));
System.out.println("round(2.675, 2, HALF_UP) = "
        + Precision.round(2.675, 2, java.math.RoundingMode.HALF_UP));
Precision.DoubleEquivalence eq1e6 = Precision.doubleEquivalenceOfEpsilon(1e-6);
System.out.println("eq1e6.eq(1.0000005, 1.0) = " + eq1e6.eq(1.0000005, 1.0));
System.out.println("eq1e6.eq(1.000002, 1.0)  = " + eq1e6.eq(1.000002, 1.0));
System.out.println("eq1e6.eq(NaN, NaN)       = " + eq1e6.eq(Double.NaN, Double.NaN));
System.out.println("representableDelta(1.0, 3) = " + Precision.representableDelta(1.0, 3));
System.out.println("representableDelta(1e300, 1e-300) = " + Precision.representableDelta(1e300, 1e-300));

实测回显:

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
eq1e6.eq(1.0000005, 1.0) = true
eq1e6.eq(1.000002, 1.0)  = false
eq1e6.eq(NaN, NaN)       = true
eq1e6.lte(1.0, 2.0)      = true
EPSILON  = 1.1102230246251565E-16
SAFE_MIN = 2.2250738585072014E-308
round(1234.5678, 2) = 1234.57
representableDelta(1.0, 3) = 3.0
representableDelta(1e300, 1e-300) = 0.0

验证结果与要点提示:equals(NaN, NaN) 返回 false 而带 IncludingNaN 的重载返回 true,与 IEEE 754 及业务直觉各有取舍,选择时注意方法名;两参版 equals(a, b) 等价于 1 ULP 比较;round 默认 HALF_EVEN(银行家舍入),2.675 舍到 2.68 是因为其二进制表示本就略大于 2.675,显式指定 RoundingMode.HALF_UP 才是日常的四舍五入语义;doubleEquivalenceOfEpsilon 把比较策略封装成可传递的对象,便于注入通用算法;representableDelta(1e300, 1e-300) 返回 0.0 说明在 1e300 附近根本无法表示 delta 为 1e-300 的增量。

3.2 ArithmeticUtils:整数运算与溢出检测

import org.apache.commons.numbers.core.ArithmeticUtils;

System.out.println("gcd(48, 18)  = " + ArithmeticUtils.gcd(48, 18));
System.out.println("gcd(-48, 18) = " + ArithmeticUtils.gcd(-48, 18));
System.out.println("lcm(4, 6)    = " + ArithmeticUtils.lcm(4, 6));
System.out.println("pow(2, 10)   = " + ArithmeticUtils.pow(2, 10));
System.out.println("pow(3, 39)   = " + ArithmeticUtils.pow(3L, 39));
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("isPowerOfTwo(1024) = " + ArithmeticUtils.isPowerOfTwo(1024));
System.out.println("isPowerOfTwo(1000) = " + ArithmeticUtils.isPowerOfTwo(1000));
System.out.println("divideUnsigned(-1, 4) = " + ArithmeticUtils.divideUnsigned(-1, 4));
System.out.println("remainderUnsigned(-1, 4) = " + ArithmeticUtils.remainderUnsigned(-1, 4));

实测回显:

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

验证结果与要点提示:pow 在结果溢出 long 时直接抛 ArithmeticException: long overflow(3^39 尚可容纳,3^40 溢出),把静默回绕变成显式故障,这是 Numbers 对 JDK Math.pow 整数版最大的增强;gcd 对负数返回正最大公约数;divideUnsigned/remainderUnsigned 把入参解释为无符号 32 位数(-1 即 4294967295),做哈希扰动时很有用。

3.3 Sum:补偿求和与点积

import org.apache.commons.numbers.core.Sum;

double naive = 0.0;
for (int i = 0; i < 10; i++) {
    naive += 0.1;
}
Sum s = Sum.create();
for (int i = 0; i < 10; i++) {
    s.add(0.1);
}
System.out.println("naive 0.1 x 10      = " + naive);
System.out.println("Sum.add(0.1) x 10   = " + s.getAsDouble());

double[] a = {1e100, 1.0, -1e100};
double[] b = {1.0, 2.0, 1.0};
double dot = 0.0;
for (int i = 0; i < a.length; i++) {
    dot += a[i] * b[i];
}
System.out.println("naive dot(a, b)     = " + dot);
System.out.println("Sum.ofProducts(a,b) = " + Sum.ofProducts(a, b).getAsDouble());

实测回显:

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

验证结果与要点提示:朴素累加 0.1 十次得到 0.9999999999999999,Sum 补偿求和得到精确的 1.0;点积案例更极端——大小交替抵消的项让朴素求和归零,Sum.ofProducts 以扩展精度给出正确答案 2.0。求和顺序敏感的统计场景(方差、损失函数)建议一律走 Sum。

3.4 DD:双双精度(double-double)

import org.apache.commons.numbers.core.DD;

System.out.println("double: 1e16 + 1       = " + (1e16 + 1));
DD dd = DD.of(1e16).add(DD.of(1));
System.out.println("DD: 1e16 + 1           = " + dd.bigDecimalValue());
System.out.println("DD hi/lo               = " + dd.hi() + " / " + dd.lo());
System.out.println("DD sqrt(2).doubleValue = " + DD.of(2).sqrt().doubleValue());

实测回显:

double: 1e16 + 1        = 1.0E16
DD: 1e16 + 1            = 10000000000000001
DD hi/lo                = 1.0E16 / 1.0
DD sqrt(2).doubleValue  = 1.4142135623730951

验证结果与要点提示:double 只有约 15.95 位十进制有效数字,1e16 量级加 1 直接丢失;DD 用一对 double(hi 主体、lo 补偿)把有效数字扩展到约 32 位,bigDecimalValue() 可以无损打印整个 hi+lo。DD 是纯 Java 实现、无依赖,适合 Kahan 求和不奏效的病态求和与长程积分。

四、angle 模块:角度单位与归一化

4.1 Angle:弧度、度、圈三种单位

import org.apache.commons.numbers.angle.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).toRad().getAsDouble()   = "
        + Angle.Deg.of(90).toRad().getAsDouble());
System.out.println("Deg.of(90).toTurn().getAsDouble()  = "
        + Angle.Deg.of(90).toTurn().getAsDouble());
System.out.println("Rad.of(3.5*pi).toDeg().getAsDouble() = "
        + Angle.Rad.of(3.5 * Math.PI).toDeg().getAsDouble());

实测回显:

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

验证结果与要点提示:Angle 是抽象类,具体值对象是其三个嵌套类 Angle.Rad、Angle.Deg、Angle.Turn,单位信息编码在类型里(toDeg() 返回 Deg 对象,getAsDouble() 取值),从根源上避免度与弧度混用——这在纯 double 传参的代码里是最常见的坐标换算 bug。

4.2 归一化:normalizer 与 Reduce

import org.apache.commons.numbers.angle.Reduce;
import java.util.function.DoubleUnaryOperator;

DoubleUnaryOperator n360 = Angle.Deg.normalizer(0);
System.out.println("Deg.normalizer(0).apply(450) = " + n360.applyAsDouble(450));
DoubleUnaryOperator n2pi = Angle.Rad.normalizer(0);
System.out.println("Rad.normalizer(0).apply(3.5*pi) = " + n2pi.applyAsDouble(3.5 * Math.PI));
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));

实测回显:

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

验证结果与要点提示:两个易错点——normalizer(lowerBound) 的唯一参数是区间下界而非周期,把角度规约到 [下界, 下界 + 单位整圆);Reduce 构造参数顺序是 (offset, period),先是下界后是周期,写反会因周期为 0 而返回 NaN(实测 new Reduce(2*Math.PI, 0.0) 对任何输入均得 NaN)。Reduce 采用两级双精度归约,输入比周期大 2^50 倍以上才会失真。

4.3 CosAngle:向量夹角余弦

import org.apache.commons.numbers.angle.CosAngle;

System.out.println("CosAngle.value([1,2,3], [2,4,6]) = "
        + CosAngle.value(new double[]{1, 2, 3}, new double[]{2, 4, 6}));
System.out.println("CosAngle.value([1,0], [0,1])     = "
        + CosAngle.value(new double[]{1, 0}, new double[]{0, 1}));

实测回显:

CosAngle.value([1,2,3], [2,4,6])   = 1.0
CosAngle.value([1,0], [0,1])       = 0.0

验证结果与要点提示:平行向量余弦 1、正交向量余弦 0;与手写 dot/(|a||b|) 相比,CosAngle 内部用扩展精度处理点积与范数,长向量或病态方向(接近反向的短向量)下更稳。两向量长度须一致且非零,零向量抛异常。

五、fraction 模块:分数与连分数

5.1 Fraction:有理数精确运算

import org.apache.commons.numbers.fraction.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("f1.divide(f2)   = " + f1.divide(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("compareTo: (-1/3) vs (-1/2) = " + Fraction.of(-1, 3).compareTo(Fraction.of(-1, 2)));

实测回显:

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

验证结果与要点提示:四个必知行为——构造即按 gcd 约分(2/4 变 1/2);1.3 只约分不翻符号,of(-3, -6) 得到 -1 / -2 而非 1/2(负分母参与 equals 与排序无碍,compareTo 在 1.3 修复了负数比较错误,输出 1 表示 -1/3 大于 -1/2,正确);整数值 toString 省略分母(of(6, 3) 输出 2);FractionException 是包级私有类,包外只能按 RuntimeException 捕获。

💡 技术拓展:Fraction 与 BigFraction 选型对比——Fraction 用两个 int 存储、运算快、但分子分母超 21 亿即溢出(乘法尤甚);BigFraction 用 BigInteger 存储、范围无限、但每次运算都有大数分配开销。经验法则:小分母(媒体时间码、比例、小数网格)用 Fraction;连乘链、密码学或天文数值用 BigFraction;拿不准时先 BigFraction 跑通再做性能收窄。本文两个示例类都已实测。

5.2 BigFraction:任意精度有理数

import org.apache.commons.numbers.fraction.BigFraction;
import java.math.RoundingMode;

BigFraction b1 = BigFraction.of(1, 3);
BigFraction b2 = BigFraction.of(1, 6);
System.out.println("b1.add(b2) = " + b1.add(b2));
System.out.println("b1.bigDecimalValue(4, HALF_UP) = " + b1.bigDecimalValue(4, RoundingMode.HALF_UP));
System.out.println("BigFraction.of(123456789012345, 987654321098765) = "
        + BigFraction.of(123456789012345L, 987654321098765L));
System.out.println("BigFraction.from(pi, 100) = " + BigFraction.from(Math.PI, 100));
System.out.println("BigFraction.from(pi, 100).doubleValue()  = "
        + BigFraction.from(Math.PI, 100).doubleValue());

实测回显:

b1.add(b2)          = 1 / 2
b1.bigDecimalValue(4, HALF_UP) = 0.3333
BigFraction.of(123456789012345, 987654321098765) = 24691357802469 / 197530864219753
BigFraction.from(pi, 100) = 22 / 7
BigFraction.from(pi, 100).doubleValue()  = 3.142857142857143

验证结果与要点提示:bigDecimalValue(4, HALF_UP) 直接给出可控舍入的十进制表示,避免手写 BigDecimal 转换;from(pi, 100) 用连分数展开法给出分母不超过 100 的最佳有理逼近 22/7,比逐位扫描法稳得多;长整型入参自动 BigInteger 化并约分。

5.3 ContinuedFraction:连分数求值

import org.apache.commons.numbers.fraction.ContinuedFraction;

// 自然常数 e 的简单连分数展开:b0=2,其后系数按 1、2、1、1、4、1、1、6 循环递增
ContinuedFraction eCF = new ContinuedFraction() {
    @Override
    protected double getA(int n, double x) {
        return 1.0;
    }
    @Override
    protected double getB(int n, double x) {
        if (n == 0) {
            return 2.0;
        }
        if (n % 3 == 2) {
            return 2.0 * ((n + 1) / 3);
        }
        return 1.0;
    }
};
double e = eCF.evaluate(0.0, 1e-15, 50);
System.out.println("e via ContinuedFraction = " + e);
System.out.println("Math.E                  = " + Math.E);
System.out.println("abs diff                = " + Math.abs(e - Math.E));

实测回显:

e via ContinuedFraction = 2.7182818284590455
Math.E                  = 2.718281828459045
abs diff                = 4.440892098500626E-16

验证结果与要点提示:ContinuedFraction 是抽象类,子类实现 getA(n, x)(分子)与 getB(n, x)(分母),求值公式为 b0 加 a1/(b1 加 a2/(b2 加更深层的同构嵌套));evaluate(x, epsilon, maxIterations) 以修正的 Lentz 算法迭代到误差达标。连分数是计算特殊函数的标准手段,适合自定义收敛级数;注意 e 这类阶乘收敛级几十项即达机器精度,而 Brouncker 圆周率连分数收敛极慢(误差约 2/N),实测 5000 项仍达不到 1e-12,选型时先评估收敛速度。

六、primes 模块:素数判定与分解

6.1 Primes:静态工具类

import org.apache.commons.numbers.primes.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]

💡 技术拓展:素数判定算法对比——试除法实现简单但 O(√n),n 到 1e9 就吃力;Miller-Rabin 概率判定配合确定性底数组合对 32 位整数是严格正确的,单次判定微秒级;埃氏筛适合一次性判定 1e7 以内的所有素数(占内存换时间)。Numbers 1.3 的 Primes.isPrime 即小素数试除加 Miller-Rabin 的混合实现,本文实测 2^31 - 1 梅森素数瞬间返回 true。

验证结果与要点提示:nextPrime 入参已是素数时原样返回;primeFactors 返回升序分解列表(带重复因子);三方法入参均为 int(最大 2147483647),超出范围抛 IllegalArgumentException。

七、complex 模块:复数

7.1 构造与四则运算

import org.apache.commons.numbers.complex.Complex;

Complex a = Complex.ofCartesian(3, 4);
Complex b = Complex.ofCartesian(1, -2);
System.out.println("a = " + a);
System.out.println("a.add(b) = " + a.add(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.arg()  = " + a.arg());
System.out.println("a.conj() = " + a.conj());
System.out.println("I.multiply(I) = " + Complex.I.multiply(Complex.I));

实测回显:

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)

验证结果与要点提示:三种静态工厂——ofCartesian(re, im) 直角坐标、ofPolar(mod, arg) 极坐标、ofCis(arg) 单位圆(实测 ofCis(pi) 得 (-1.0, 1.2246467991473532E-16),虚部残留来自 pi 的有限精度);方法名是 conj()/arg()/abs(),与 math3 的 conjugate()/getArgument()/abs() 不同;实部虚部常量 Complex.I 的平方严格等于 -1。

7.2 超越函数与开方

System.out.println("a.exp()  = " + a.exp());
System.out.println("a.sqrt() = " + a.sqrt());
System.out.println("a.nthRoot(3) = " + a.nthRoot(3));

实测回显:

a.exp()          = (-13.128783081462158,-15.200784463067954)
a.sqrt()         = (2.0,1.0)
a.nthRoot(3)     = [(1.6289371459221758,0.5201745023045458), (-1.2649529063577512,1.1506136983844508), (-0.36398423956442444,-1.6707882006889963)]

验证结果与要点提示:(3+4i)^0.5 = 2+i 可手算验证;nthRoot(k) 返回全部 k 个根(按辐角顺序);pow(2) 实测 (-6.999999999999997, 24.0) 实部带 3e-15 误差——复数幂走 exp/log 路径,纯整数次幂若需严格结果应手写乘法。

八、quaternion 模块:四元数

8.1 构造与 Hamilton 乘法

import org.apache.commons.numbers.quaternion.Quaternion;

System.out.println("I.multiply(J) = " + Quaternion.I.multiply(Quaternion.J));
System.out.println("J.multiply(K) = " + Quaternion.J.multiply(Quaternion.K));
System.out.println("K.multiply(I) = " + Quaternion.K.multiply(Quaternion.I));
Quaternion p = Quaternion.of(1.0, new double[]{2.0, 3.0, 4.0});
System.out.println("p             = " + p);
System.out.println("p.normSq()    = " + p.normSq());
Quaternion q = Quaternion.of(0.7071067811865476, 0.7071067811865476, 0.0, 0.0);
System.out.println("q.norm()      = " + q.norm());
System.out.println("q.isUnit(1e-10) = " + q.isUnit(1e-10));
System.out.println("q.conjugate() = " + q.conjugate());

实测回显:

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]
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
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]
slerp(0.5)    = [0.5000000000000001 0.5000000000000001 0.0 0.7071067811865476]

验证结果与要点提示:Hamilton 乘法 i×j=k、j×k=i、k×i=i 系全部实测成立;of(w, double 数组) 用标量加三维向量构造,getScalarPart/getVectorPart 对应取出;四元数乘法不满足交换律,Quaternion.multiply(I, J) 静态写法与 I.multiply(J) 实例写法结果一致(实测同值);unit 判定 isUnit(tol) 内部用到 core 的 Precision,纯 jar 直跑场景必须把 commons-numbers-core-1.3.jar 放进 classpath。

8.2 Slerp:球面线性插值

import org.apache.commons.numbers.quaternion.Slerp;

Quaternion q = Quaternion.of(0.7071067811865476, 0.7071067811865476, 0.0, 0.0);
Quaternion r = Quaternion.of(0.0, 0.0, 0.0, 1.0);
Slerp slerp = new Slerp(q, r);
System.out.println("slerp(0.0) = " + slerp.apply(0.0));
System.out.println("slerp(0.5) = " + slerp.apply(0.5));
System.out.println("slerp(1.0) = " + slerp.apply(1.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]

验证结果与要点提示:slerp 的 apply(t) 要求 t 在 [0, 1],两端点精确还原;与对四元数分量做线性插值再归一化(nlerp)相比,slerp 保证角速度恒定,是相机轨迹与骨骼动画的标准做法。

九、gamma 模块:特殊函数

9.1 Gamma 函数族

import org.apache.commons.numbers.gamma.Gamma;
import org.apache.commons.numbers.gamma.LogGamma;
import org.apache.commons.numbers.gamma.Digamma;
import org.apache.commons.numbers.gamma.Trigamma;

System.out.println("Gamma.value(5.0)    = " + Gamma.value(5.0));
System.out.println("Gamma.value(0.5)    = " + Gamma.value(0.5));
System.out.println("Gamma.value(100.0)  = " + Gamma.value(100.0));
System.out.println("LogGamma.value(100.0) = " + LogGamma.value(100.0));
System.out.println("Math.log(Gamma.value(100)) = " + Math.log(Gamma.value(100.0)));
System.out.println("Digamma.value(1.0)  = " + Digamma.value(1.0));
System.out.println("Trigamma.value(1.0) = " + Trigamma.value(1.0));

实测回显:

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

验证结果与要点提示:Gamma.value(n+1) = n!(实测 5.0 得 4! = 24),Gamma.value(0.5) = sqrt(pi);超过 171 后 Gamma 直接溢出为无穷,而 LogGamma 全程可用——99! 的对数 359.1342053695754 与先算 Gamma 再取对数的核对值逐位一致;Digamma.value(1) 理论值是负欧拉常数 -0.5772156649015329,实测 -0.5772156649015314,误差 1.5e-15,符合 double 精度预期。

9.2 误差函数与 Beta 函数

import org.apache.commons.numbers.gamma.Erf;
import org.apache.commons.numbers.gamma.Erfc;
import org.apache.commons.numbers.gamma.InverseErfc;
import org.apache.commons.numbers.gamma.Beta;

System.out.println("Erf.value(1.0)  = " + Erf.value(1.0));
System.out.println("Erfc.value(1.0) = " + Erfc.value(1.0));
System.out.println("InverseErfc.value(Erfc.value(1.0)) = " + InverseErfc.value(Erfc.value(1.0)));
System.out.println("Beta.value(2, 3)    = " + Beta.value(2, 3));
System.out.println("LogBeta.value(2, 3) = " + org.apache.commons.numbers.gamma.LogBeta.value(2, 3));

实测回显:

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

验证结果与要点提示:Erf 即标准正态分布 CDF 的核心(P(|Z| < 1) = erf(1/sqrt(2))),配合 InverseErfc 可做正态分位数换算(实测逆运算精确还原 1.0);Beta(2,3) = Γ(2)Γ(3)/Γ(5) = 1×2/24 = 1/12 ≈ 0.08333,与实测一致;大参数场景一律走 LogBeta。

十、combinatorics 模块:组合数学

10.1 阶乘:精确与浮点两条路

import org.apache.commons.numbers.combinatorics.Factorial;
import org.apache.commons.numbers.combinatorics.LogFactorial;

System.out.println("Factorial.value(20)        = " + Factorial.value(20));
System.out.println("Factorial.doubleValue(170) = " + Factorial.doubleValue(170));
try {
    System.out.println("Factorial.value(21)        = " + Factorial.value(21));
} catch (RuntimeException e) {
    System.out.println("Factorial.value(21)        = " + e.getClass().getSimpleName() + ": " + e.getMessage());
}
System.out.println("LogFactorial.create().value(1000) = " + LogFactorial.create().value(1000));

实测回显:

Factorial.value(20)          = 2432902008176640000
Factorial.doubleValue(170)   = 7.257415615307999E306
Factorial.value(21)          = CombinatoricsException: Number 21 is out of range [0, 20]
LogFactorial.create().value(1000)       = 5912.128178488163

💡 技术拓展:阶乘溢出应对选型——n 不超过 20 用 Factorial.value(long 精确值,超出抛 CombinatoricsException);n 不超过 170 用 Factorial.doubleValue(浮点近似,再大溢出为无穷);任意规模或参与除法约分的表达式用 LogFactorial(对数空间,1000! 的对数照常计算)。组合数同理对应 BinomialCoefficient、BinomialCoefficientDouble、LogBinomialCoefficient 三件套。本文三类均已实测。

10.2 二项式系数与斯特林数

import org.apache.commons.numbers.combinatorics.BinomialCoefficient;
import org.apache.commons.numbers.combinatorics.BinomialCoefficientDouble;
import org.apache.commons.numbers.combinatorics.LogBinomialCoefficient;
import org.apache.commons.numbers.combinatorics.Stirling;

System.out.println("BinomialCoefficient.value(52, 5) = " + BinomialCoefficient.value(52, 5));
System.out.println("BinomialCoefficient.value(66, 33) = " + BinomialCoefficient.value(66, 33));
try {
    System.out.println("BinomialCoefficient.value(67, 33) = " + BinomialCoefficient.value(67, 33));
} catch (RuntimeException e) {
    System.out.println("BinomialCoefficient.value(67, 33) = " + e.getClass().getSimpleName() + ": " + e.getMessage());
}
System.out.println("BinomialCoefficientDouble.value(67, 33) = "
        + BinomialCoefficientDouble.value(67, 33));
System.out.println("LogBinomialCoefficient.value(1000, 500) = "
        + LogBinomialCoefficient.value(1000, 500));
System.out.println("Stirling.stirlingS2(5, 2) = " + Stirling.stirlingS2(5, 2));
System.out.println("Stirling.stirlingS1(4, 2) = " + Stirling.stirlingS1(4, 2));

实测回显:

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
Stirling.stirlingS2(5, 2)    = 15
Stirling.stirlingS1(4, 2)    = 11

验证结果与要点提示:C(66,33) 是 long 能容纳的极限组合数(7.2e18),C(67,33) 溢出即抛异常,而非返回负数——溢出行为可预期;扑克牌型数 C(52,5) = 2598960 可手算复核;斯特林第二类数 S(5,2) = 15 表示把 5 个元素划分为 2 个非空集合的方式数。

10.3 Combinations:组合枚举

import org.apache.commons.numbers.combinatorics.Combinations;
import java.util.Iterator;

Combinations c = Combinations.of(5, 2);
System.out.println("Combinations.of(5, 2): n=" + c.getN() + " k=" + c.getK());
int count = 0;
StringBuilder first5 = new StringBuilder();
Iterator<int[]> it = c.iterator();
while (it.hasNext()) {
    int[] combo = it.next();
    if (count < 5) {
        first5.append(java.util.Arrays.toString(combo)).append(" ");
    }
    count++;
}
System.out.println("前 5 组合: " + first5.toString().trim());
System.out.println("组合总数  = " + count);

实测回显:

Combinations.of(5, 2): n=5 k=2
前 5 组合: [0, 1] [0, 2] [1, 2] [0, 3] [1, 3]
组合总数      = 10(= C(5,2) = 10)

验证结果与要点提示:Combinations 实现 Iterable,按字典序惰性产出组合,不物化 C(n,k) 个数组,适合大 n 小 k 的遍历;枚举总数实测 10,与公式一致。

十一、arrays 模块:数组算法

11.1 Selection:顺序统计量(top-k)

import org.apache.commons.numbers.arrays.Selection;
import java.util.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));
System.out.println("a[3] (第4小的数) = " + copy[3]);
double[] copy2 = data.clone();
Selection.select(copy2, new int[]{0, 9});
System.out.println("after select(copy, [0, 9]): " + Arrays.toString(copy2));

实测回显:

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]

验证结果与要点提示:select(data, k) 是原地部分排序,只保证第 k 位为第 k 小(0 基),其余按快排分区落位,不必全排序;关键索引形式 select(data, int 数组) 一次定位多个分位点,实测传入 {0, 9} 后整个数组升序就位(最小与最大分位拉通整个数组)。适合求中位数、分位数与 top-k,平均 O(n)。

11.2 SortInPlace:键值同步排序

import org.apache.commons.numbers.arrays.SortInPlace;

double[] keys = {3.0, 1.0, 2.0};
double[] vals = {30.0, 10.0, 20.0};
SortInPlace.ASCENDING.apply(keys, vals);
System.out.println("keys   = " + Arrays.toString(keys));
System.out.println("values = " + Arrays.toString(vals));

实测回显:

keys   = [1.0, 2.0, 3.0]
values = [10.0, 20.0, 30.0]

验证结果与要点提示:按 keys 升序(SortInPlace.DESCENDING 为降序)重排 keys 并把 vals 同步置换,保持键值配对;典型用途是按权重排序候选集后保留对应对象引用索引。

11.3 MultidimensionalCounter:多维索引换算

import org.apache.commons.numbers.arrays.MultidimensionalCounter;

MultidimensionalCounter counter = MultidimensionalCounter.of(2, 3, 4);
System.out.println("getSize()  = " + counter.getSize());
System.out.println("getSizes() = " + Arrays.toString(counter.getSizes()));
System.out.println("toUni(1, 2, 3) = " + counter.toUni(1, 2, 3));
System.out.println("toMulti(23)    = " + Arrays.toString(counter.toMulti(23)));
System.out.println("toMulti(0)     = " + Arrays.toString(counter.toMulti(0)));

实测回显:

getSize()           = 24
getSizes()          = [2, 3, 4]
toUni(1, 2, 3)      = 23
toMulti(23)         = [1, 2, 3]
toMulti(0)          = [0, 0, 0]

验证结果与要点提示:行主序换算,(1,2,3) 在 2×3×4 张量中的线性下标为 1×12 + 2×4 + 3 = 23,且 toMulti(23) 实测还原为 [1, 2, 3],互为逆运算;下标越界抛 IndexOutOfBoundsException。注意 1.3 的 arrays 模块已无旧文档提到的 DoubleArray 类(1.0 稳定版前已移除),数组封装直接用 JDK 结构。

十二、field 与 rootfinder 模块

12.1 field:域代数抽象

import org.apache.commons.numbers.field.FP64;
import org.apache.commons.numbers.field.FP64Field;
import org.apache.commons.numbers.field.FractionField;

FP64 x = FP64.of(6);
FP64 y = FP64.of(4);
System.out.println("x.add(y)       = " + x.add(y));
System.out.println("x.divide(y)    = " + x.divide(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

验证结果与要点提示:Field 接口统一 plus/times/reciprocal 等域运算,FP64、Fraction、BigFraction、DD 各有对应 Field 单例;泛型算法只面对 Field,可零成本切换数值类型(同一套高斯消元既跑 double 也跑分数)。

12.2 rootfinder:Brent 法求根

import org.apache.commons.numbers.rootfinder.BrentSolver;
import java.util.function.DoubleUnaryOperator;

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);
System.out.println("f1(root)                  = " + f1.applyAsDouble(r1));
DoubleUnaryOperator f2 = Math::cos;
double r2 = new BrentSolver(1e-15, 1e-15, 1e-15).findRoot(f2, 0.0, 2.0);
System.out.println("root of cos(x) in [0, 2]  = " + r2);

实测回显:

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;x^2-2 的根 1.4142135623731364 与真值相差 4e-13,恰在 1e-12 函数值精度内,比 math3 的 Pegasus/Illinois 更贴近原始 Brent 实现的收敛判据。

十三、API 使用注意事项

  1. 静态工厂优先:所有值对象用 of 系列构造(Fraction.of、Complex.ofCartesian、Quaternion.of),构造器不开放,API 演进不会被实现细节绑死。
  2. 符号归一缺失:Fraction.of(-3, -6) 结果是 -1 / -2,1.3 仅按 gcd 约分;跨模块传递前如有需要自行归一(分母为负时分子分母同取反)。
  3. 异常可见性:FractionException、CombinatoricsException 等异常类是包级私有,catch 用公共父类(RuntimeException 或 ArithmeticException),具体类型看方法文档。
  4. 溢出即异常:ArithmeticUtils.pow、BinomialCoefficient.value、Factorial.value 溢出直接抛异常而非回绕,防御式代码要 catch 或改用 Double/Log 系列变体。
  5. 角度归一化参数序:Angle.*.normalizer(lowerBound) 的参数是下界;Reduce(offset, period) 先下界后周期,顺序反了得 NaN。
  6. 传递依赖:fraction、angle、complex、quaternion、field 依赖 core;quaternion 的 isUnit 在运行期加载 core 的 Precision,离线 jar 直跑时别漏掉 core 包。
  7. 与 math3 命名差异速查:conj() 对 math3 conjugate()、arg() 对 getArgument()、构造一律静态工厂、素数判定升级为 Miller-Rabin。

十四、参考链接