在研究Java的BigInteger乘法操作的源码时,在JDK的实现里看到了三种算法,调用multiply会根据两个乘数的大小进入不同的算法进行进一步的计算,他们分别是:
- 小学生算法(Grade-School Algorithm)
- Karatsuba算法(Karatsuba Algorithm | Brilliant Math & Science Wiki)
- Toom Cook-3算法
下面分别讲解各个算法,前面两种算法是比较好理解和实现的,而最后一种算法是比较复杂的。当然,本篇文章重点讲解三种算法的原理,实现部分参考JDK源码(BigInteger.java)即可。根据我的计划,之后的会有自己实现大数乘法的文章(也许会直接在此篇文章上增添)。
小学生算法
小学生算法,见名知意,就是小学数学课上学过的列竖式的方法,相比于下面两种算法,这个算法的思想和理论上的效果都显得很low。
那为什么JDK里还会采用这种算法而不是一股脑的用高级算法?这是因为下面这些甚至还有更高级的算法虽然在渐进意义上优势满满,但是时间复杂度前面的常数可不小,这样就导致在乘数比较小的时候,此算法还是具有优势的(比如JDK中,当两个乘数的二进制位数都大于 80 × 32 80\times32 80×32时才会采用下面的算法,否则的就直接利用小学生算法计算)。
算法思路
两个乘数 X X X和 Y Y Y,分别用 X X X的每一位和 Y Y Y的每一位相乘,将结果保存到对应的位置并且同时保留进位,每次相乘时将上次的进位加上同时也要将当前结果的对应位置的位加上。
令设 X , Y , Z X, Y, Z X,Y,Z为三个整数,其中 Z Z Z的位数等于 X X X和 Y Y Y的位数之和, 要求计算 X × Y X\times Y X×Y并将结果存至 Z Z Z中;
X X X, Y Y Y的位数分别为 4 4 4, 2 2 2;
设 N i N_i Ni表示整数 N N N的第 i i i位数,第 i i i位是从低位到高位的从 0 0 0开始计数的第 i i i位;
算法过程如下图 :
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
上图是通常的手算算法, 编程实现时, 还需要变换一下思路。手列竖式时,算完所有的行再算出 Z Z Z的所有的位;编程时,每算一行时就算出此时的 Z Z Z的对应的某一位,基本算法和手算相同(也需加上上一位的进位),不同的是还需加上 Z Z Z的这一位之前的值。
JDK源码分析
@HotSpotIntrinsicCandidate // 该注解说明HotSpot内有本地的实现, 调用算法时会用本地实现替之
private static int[] implMultiplyToLen(int[] x, int xlen, int[] y, int ylen, int[] z) {
int xstart = xlen - 1;
int ystart = ylen - 1;
if (z == null || z.length < (xlen + ylen))
z = new int[xlen + ylen]; // 开辟z数组以存放结果
long carry = 0;
// 计算第一行
// 第一行能算出Z的第0位到第xstart - 1位
for (int j = ystart, k = ystart + 1 + xstart; j >= 0; j--, k--) {
long product = (y[j] & LONG_MASK) * // 相乘加上上次的进位
(x[xstart] & LONG_MASK) + carry;
z[k] = (int)product;
carry = product >>> 32;
}
z[xstart] = (int)carry;
for (int i = xstart - 1; i >= 0; i--) {
// 计算其余行
carry = 0;
for (int j = ystart, k = ystart + 1 + i; j >= 0; j--, k--) {
long product = (y[j] & LONG_MASK) * // 相乘加上上次的进位, 同
(x[i] & LONG_MASK) + // 时还要加上该位之前的值
(z[k] & LONG_MASK) + carry;
z[k] = (int)product;
carry = product >>> 32;
}
z[i] = (int)carry;
}
return z;
}
易得此算法的时间复杂度为平方级
Karatsuba算法
Karatsuba算法的思想是分而治之。
算法思路
该算法的思路是比较简单的,将两个乘数二分(设每一半位数为 h h h),
即令 X = X h ⋅ 2 h + X l , Y = Y h ⋅ 2 h + Y l X=X_h \cdot2^{h}+X_l,Y=Y_h \cdot2^{h}+Y_l X=Xh⋅2h+Xl,Y=Yh⋅2h+Yl
则
X Y XY XY
= ( X h ⋅ 2 h + X l ) ( Y h ⋅ 2 h + Y l ) =(X_h \cdot2^{h}+X_l)(Y_h \cdot2^{h}+Y_l) =(Xh⋅2h+Xl)(Yh⋅2h+Yl)
= X h Y h ⋅ 2 2 h + X h Y l ⋅ 2 h + X l Y h ⋅ 2 h + X l Y l =X_hY_h\cdot2^{2h}+X_hY_l\cdot2^h+X_lY_h\cdot2^h+X_lY_l =XhYh⋅22h+XhYl⋅2h+XlYh⋅2h+XlYl
= X h Y h ⋅ 2 2 h + ( X h Y l + X l Y h ) ⋅ 2 h + X l Y l =X_hY_h\cdot2^{2h}+(X_hY_l+X_lY_h)\cdot2^h+X_lY_l =XhYh⋅22h+(XhYl+XlYh)⋅2h+
Java BigInteger乘法算法解析:从小学生到Toom-Cook-3

本文深入探讨了Java中BigInteger的乘法运算实现,包括小学生算法、Karatsuba算法和Toom-Cook-3算法。通过源码分析,解释了各种算法的工作原理和效率,并指出在不同情况下哪种算法更具优势。尽管高级算法如Toom-Cook-3在理论上具有更好的时间复杂度,但实际应用中可能因常数因子而不如简单算法。
最低0.47元/天 解锁文章
1755

被折叠的 条评论
为什么被折叠?



