目录
java大数运算详解【其三】大数乘法之平方算法之按位二次展开式算法
java大数运算详解【其四】大数乘法之平方算法之Karatsuba平方算法
java大数运算详解【其五】大数乘法之平方算法之ToomCook3平方算法
java大数运算详解【其七】大数乘法之Karatsuba乘法和ToomCook3乘法
java大数运算详解【其九】大数除法之试商法(Knuth除法)核心算法
java大数运算详解【其十】大数除法之Burnikel-Ziegler除法算法
4、Karatsuba乘法(二分展开式乘法)
/**
* 使用Karatsuba乘法算法将两个大整数相乘。
* 这是一种递归的分治算法,与多聚类算法中通常使用的“小学”算法相比,它在处理大数问题时效率更高。
* 如果进行乘法的数组长度为n,“小学”算法的渐近O(n ^ 2)的复杂性。
* 相比之下,Karatsuba算法的复杂性O(n ^(log2(3))),或O(n ^ 1.585)。
* 在对过程进行评估时,它通过执行3次乘法而不是4次来实现这种性能提升。
* 由于它有一些开销,当两个数字都大于某个阈值时(通过实验发现),应该使用它。
*
* See: http://en.wikipedia.org/wiki/Karatsuba_algorithm
*/
private static BigInteger multiplyKaratsuba(BigInteger x, BigInteger y) {
int xlen = x.mag.length;
int ylen = y.mag.length;
// 每个数组的一半的长度。
int half = (Math.max(xlen, ylen)+1) / 2;
// xl和yl分别是x和y的下半部,xh和yh是上半部。
BigInteger xl = x.getLower(half);
BigInteger xh = x.getUpper(half);
BigInteger yl = y.getLower(half);
BigInteger yh = y.getUpper(half);
BigInteger p1 = xh.multiply(yh); // p1 = xh*yh
BigInteger p2 = xl.multiply(yl); // p2 = xl*yl
// p3=(xh+xl)*(yh+yl)
BigInteger p3 = xh.add(xl).multiply(yh.add(yl));
// result = p1 * 2^(32*2*half) + (p3 - p1 - p2) * 2^(32*half) + p2
BigInteger result = p1.shiftLeft(32*half).add(p3.subtract(p1).subtract(p2)).shiftLeft(32*half).add(p2);
if (x.signum != y.signum) {
return result.negate();
} else {
return result;
}
}
5、ToomCook3乘法(三分展开式乘法)
/**
* 使用3路Toom-Cook乘法算法将两个大整数相乘。
* 这是一种递归的分治算法,与多聚类算法中通常使用的“小学”算法相比,它在处理大数问题时效率更高。
* 如果进行乘法的数组长度为n,“小学”算法的渐近O(n ^ 2)的复杂性。
* 相比之下,3路Toom-Cook的复杂性为O(n ^ 1.465)。
* 它通过将每个数字分成三部分,并在计算乘积时进行5次而不是9次的乘法,从而实现这种渐近性能的提高。
* 由于图姆-库克算法的开销(加法、移位和除法),它只能在两个数字都大于某个阈值时使用(实验发现)。
* 这个阈值通常比卡拉图巴乘法的阈值大,所以这种算法通常只在数字明显变大时才使用。
*
* 使用的算法是Marco Bodrato概述的“最优”3路Toom-Cook算法。
* See: http://bodrato.it/toom-cook/
* http://bodrato.it/papers/#WAIFI2007
*/
private static BigInteger multiplyToomCook3(BigInteger a, BigInteger b) {
int alen = a.mag.length;
int blen = b.mag.length;
int largest = Math.max(alen, blen);
// k是低阶切片的大小(以int表示)。
int k = (largest+2)/3; // Equal to ceil(largest/3)
// r是最高阶片的大小(以int为单位)。
int r = largest - 2*k;
// 获取这些数字的切片。a2和b2是数字中最重要的位(高位),a0和b0是最不重要的位(低位)。
BigInteger a0, a1, a2, b0, b1, b2;
a2 = a.getToomSlice(k, r, 0, largest);
a1 = a.getToomSlice(k, r, 1, largest);
a0 = a.getToomSlice(k, r, 2, largest);
b2 = b.getToomSlice(k, r, 0, largest);
b1 = b.getToomSlice(k, r, 1, largest);
b0 = b.getToomSlice(k, r, 2, largest);
BigInteger v0, v1, v2, vm1, vinf, t1, t2, tm1, da1, db1;
v0 = a0.multiply(b0);
da1 = a2.add(a0);
db1 = b2.add(b0);
vm1 = da1.subtract(a1).multiply(db1.subtract(b1));
da1 = da1.add(a1);
db1 = db1.add(b1);
v1 = da1.multiply(db1);
v2 = da1.add(a2).shiftLeft(1).subtract(a0).multiply(
db1.add(b2).shiftLeft(1).subtract(b0));
vinf = a2.multiply(b2);
// 这个算法需要两个2的除法和一个3的除法。
// 所有的除法都是精确的,也就是说,它们不产生余数,所有的结果都是肯定的。
// 以2为单位的除法是按右移执行的,这样比较有效,只剩下以3为单位的除法。
// 3的除法是由这种情况下的优化算法完成的。
t2 = v2.subtract(vm1).exactDivideBy3();
tm1 = v1.subtract(vm1).shiftRight(1);
t1 = v1.subtract(v0);
t2 = t2.subtract(t1).shiftRight(1);
t1 = t1.subtract(tm1).subtract(vinf);
t2 = t2.subtract(vinf.shiftLeft(1));
tm1 = tm1.subtract(t2);
// 向左移位的位数。
int ss = k*32;
BigInteger result = vinf.shiftLeft(ss).add(t2).shiftLeft(ss).add(t1).shiftLeft(ss).add(tm1).shiftLeft(ss).add(v0);
if (a.signum != b.signum) {
return result.negate();
} else {
return result;
}
}