算法背景
高精度计算(Arbitrary-Precision Arithmetic)是处理超出原生数据类型范围数值的基础技术。Java的BigInteger和BigDecimal虽提供了现成支持,但理解其底层实现原理对于算法竞赛、密码学开发和数值分析至关重要。
本文将从零实现大整数的核心运算,揭示高精度计算的算法本质,并应用于超大数阶乘、斐波那契数列等经典问题。
核心思想
将大整数按固定基数(通常为 $10^4$ 或 $10^9$)分解为数组存储,模拟人工竖式运算:
- 加法: 逐位相加,处理进位
- 减法: 逐位相减,处理借位
- 乘法: 双重循环,$O(n^2)$ 复杂度
- 除法: 试商法或牛顿迭代法
- 进制转换: 短除法或快速幂分解
Java实现:大整数类
import java.util.*;
public class BigInt {
// 每元素存储4位十进制数,平衡效率与实现复杂度
private static final int BASE = 10000;
private static final int WIDTH = 4;
// 小端存储: digits[0]是最低位
private final int[] digits;
private final boolean negative;
public BigInt(String s) {
if (s.startsWith("-")) {
this.negative = true;
s = s.substring(1);
} else {
this.negative = false;
}
// 从字符串构建数字数组
int len = s.length();
int n = (len + WIDTH - 1) / WIDTH;
digits = new int[n];
for (int i = 0; i < n; i++) {
int end = len - i * WIDTH;
int start = Math.max(0, end - WIDTH);
digits[i] = Integer.parseInt(s.substring(start, end));
}
trim();
}
private BigInt(int[] digits, boolean negative) {
this.digits = digits;
this.negative = negative;
trim();
}
/** 去除前导零 */
private void trim() {
int n = digits.length;
while (n > 1 && digits[n - 1] == 0) n--;
}
/** 判断是否为0 */
public boolean isZero() {
return digits.length == 1 && digits[0] == 0;
}
/** 取绝对值 */
public BigInt abs() {
return new BigInt(digits.clone(), false);
}
/** 比较绝对值大小: 返回 1, 0, -1 */
private int compareAbs(BigInt other) {
if (digits.length != other.digits.length) {
return digits.length > other.digits.length ? 1 : -1;
}
for (int i = digits.length - 1; i >= 0; i--) {
if (digits[i] != other.digits[i]) {
return digits[i] > other.digits[i] ? 1 : -1;
}
}
return 0;
}
/** 加法: 假设两数同号 */
private BigInt addAbs(BigInt other) {
int n = Math.max(digits.length, other.digits.length);
int[] res = new int[n + 1];
int carry = 0;
for (int i = 0; i < n; i++) {
int a = i < digits.length ? digits[i] : 0;
int b = i < other.digits.length ? other.digits[i] : 0;
int sum = a + b + carry;
res[i] = sum % BASE;
carry = sum / BASE;
}
if (carry > 0) res[n] = carry;
return new BigInt(res, false);
}
/** 减法: 假设 |this| >= |other| */
private BigInt subAbs(BigInt other) {
int[] res = new int[digits.length];
int borrow = 0;
for (int i = 0; i < digits.length; i++) {
int a = digits[i];
int b = i < other.digits.length ? other.digits[i] : 0;
int diff = a - b - borrow;
if (diff < 0) {
diff += BASE;
borrow = 1;
} else {
borrow = 0;
}
res[i] = diff;
}
return new BigInt(res, false);
}
/** 公开加法 */
public BigInt add(BigInt other) {
if (negative == other.negative) {
return new BigInt(addAbs(other).digits, negative);
}
// 异号相当于减法
int cmp = compareAbs(other);
if (cmp == 0) return new BigInt("0");
if (cmp > 0) {
return new BigInt(subAbs(other).digits, negative);
} else {
return new BigInt(other.subAbs(this).digits, other.negative);
}
}
/** 公开减法 */
public BigInt subtract(BigInt other) {
return add(new BigInt(other.digits, !other.negative));
}
/** 乘法 */
public BigInt multiply(BigInt other) {
if (isZero() || other.isZero()) return new BigInt("0");
int[] res = new int[digits.length + other.digits.length];
for (int i = 0; i < digits.length; i++) {
long carry = 0;
for (int j = 0; j < other.digits.length || carry > 0; j++) {
long cur = res[i + j] + (long)digits[i] *
(j < other.digits.length ? other.digits[j] : 0) + carry;
res[i + j] = (int)(cur % BASE);
carry = cur / BASE;
}
}
return new BigInt(res, negative != other.negative);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (negative && !isZero()) sb.append('-');
sb.append(digits[digits.length - 1]);
for (int i = digits.length - 2; i >= 0; i--) {
sb.append(String.format("%0" + WIDTH + "d", digits[i]));
}
return sb.toString();
}
public static void main(String[] args) {
BigInt a = new BigInt("12345678901234567890");
BigInt b = new BigInt("98765432109876543210");
System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("a + b = " + a.add(b));
System.out.println("a - b = " + a.subtract(b));
System.out.println("a * b = " + a.multiply(b));
// 验证与Java BigInteger一致
java.math.BigInteger ja = new java.math.BigInteger("12345678901234567890");
java.math.BigInteger jb = new java.math.BigInteger("98765432109876543210");
System.out.println("\nJava BigInteger verification:");
System.out.println("a + b = " + ja.add(jb));
System.out.println("a - b = " + ja.subtract(jb));
System.out.println("a * b = " + ja.multiply(jb));
}
}
应用:超大数阶乘计算
public class Factorial {
/**
* 计算n!的高精度结果
* 使用递推: n! = (n-1)! * n
* 时间复杂度: O(n^2 * log(n))
*/
public static BigInt factorial(int n) {
BigInt result = new BigInt("1");
for (int i = 2; i <= n; i++) {
result = result.multiply(new BigInt(String.valueOf(i)));
}
return result;
}
/**
* 分治阶乘: 利用递归分解减少大数乘法次数
* product(l, r) = l * (l+1) * ... * r
*/
public static BigInt factorialDivideConquer(int n) {
return product(1, n);
}
private static BigInt product(int l, int r) {
if (l > r) return new BigInt("1");
if (l == r) return new BigInt(String.valueOf(l));
int mid = l + (r - l) / 2;
return product(l, mid).multiply(product(mid + 1, r));
}
public static void main(String[] args) {
// 计算100!
BigInt fact100 = factorial(100);
System.out.println("100! = " + fact100);
System.out.println("Digits in 100!: " + fact100.toString().length());
// 计算1000! (分治法)
BigInt fact1000 = factorialDivideConquer(1000);
String s = fact1000.toString();
System.out.println("\n1000! digits: " + s.length());
System.out.println("First 50 digits: " + s.substring(0, 50));
System.out.println("Last 50 digits: " + s.substring(s.length() - 50));
}
}
应用:高精度斐波那契数列
public class FibonacciBig {
/**
* 快速倍增法计算斐波那契数列
* 利用恒等式:
* F(2k) = F(k) * [2*F(k+1) - F(k)]
* F(2k+1) = F(k+1)^2 + F(k)^2
* 时间复杂度: O(log n) 次大数乘法
*/
static class Pair {
BigInt f, g; // F(k), F(k+1)
Pair(BigInt f, BigInt g) { this.f = f; this.g = g; }
}
public static Pair fibDoubling(int n) {
if (n == 0) return new Pair(new BigInt("0"), new BigInt("1"));
Pair p = fibDoubling(n >> 1);
BigInt a = p.f; // F(k)
BigInt b = p.g; // F(k+1)
// c = F(2k) = F(k) * [2*F(k+1) - F(k)]
BigInt twoB = b.add(b);
BigInt twoBMinusA = twoB.subtract(a);
BigInt c = a.multiply(twoBMinusA);
// d = F(2k+1) = F(k)^2 + F(k+1)^2
BigInt d = a.multiply(a).add(b.multiply(b));
if ((n & 1) == 0) {
return new Pair(c, d);
} else {
// F(2k+1) = d, F(2k+2) = c + d
return new Pair(d, c.add(d));
}
}
public static void main(String[] args) {
// 计算第1000个斐波那契数
Pair p = fibDoubling(1000);
String fib1000 = p.f.toString();
System.out.println("F(1000) digits: " + fib1000.length());
System.out.println("F(1000) = " + fib1000);
// 对比Java BigInteger
java.math.BigInteger a = java.math.BigInteger.ZERO;
java.math.BigInteger b = java.math.BigInteger.ONE;
for (int i = 2; i <= 1000; i++) {
java.math.BigInteger c = a.add(b);
a = b;
b = c;
}
System.out.println("\nJava verification matches: " + fib1000.equals(a.toString()));
}
}
应用:大数幂运算(快速幂)
public class BigIntPower {
/**
* 快速幂算法: 计算 base^exp
* 时间复杂度: O(log exp) 次乘法
*/
public static BigInt pow(BigInt base, int exp) {
BigInt result = new BigInt("1");
BigInt cur = base;
while (exp > 0) {
if ((exp & 1) == 1) {
result = result.multiply(cur);
}
cur = cur.multiply(cur);
exp >>= 1;
}
return result;
}
public static void main(String[] args) {
// 计算 2^1000
BigInt two = new BigInt("2");
BigInt pow1000 = pow(two, 1000);
System.out.println("2^1000 = " + pow1000);
System.out.println("Digits: " + pow1000.toString().length());
}
}
复杂度分析
| 运算 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 加法 | $O(n)$ | $O(n)$ |
| 减法 | $O(n)$ | $O(n)$ |
| 乘法(普通) | $O(n^2)$ | $O(n)$ |
| 乘法(Karatsuba) | $O(n^{1.585})$ | $O(n)$ |
| 乘法(FFT) | $O(n \log n)$ | $O(n)$ |
| 除法 | $O(n^2)$ | $O(n)$ |
| 阶乘(递推) | $O(n^2 \log n)$ | $O(n \log n)$ |
| 阶乘(分治) | $O(M(n) \log n)$ | $O(n \log n)$ |
| 快速幂 | $O(\log e \cdot M(n))$ | $O(n)$ |
其中 $n$ 为数字位数,$M(n)$ 为乘法复杂度。
工程实践建议
- 生产环境: 优先使用Java标准库
BigInteger,经过充分优化和测试 - 算法竞赛: 理解底层实现有助于处理特殊进制或自定义运算
- 密码学: 需使用蒙哥马利约减、Barrett约减等优化技术
- GPU加速: 大整数运算可并行化,适合CUDA/OpenCL实现
高精度计算是连接理论数学与工程实现的桥梁,掌握其原理能够应对各类极端数值场景。