目录
java大数运算详解【其三】大数乘法之平方算法之按位二次展开式算法
java大数运算详解【其四】大数乘法之平方算法之Karatsuba平方算法
java大数运算详解【其五】大数乘法之平方算法之ToomCook3平方算法
java大数运算详解【其七】大数乘法之Karatsuba乘法和ToomCook3乘法
java大数运算详解【其九】大数除法之试商法(Knuth除法)核心算法
java大数运算详解【其十】大数除法之Burnikel-Ziegler除法算法
一、大数加法
/**
* 返回值为{@code (this + val)}的大型整数。
*
* @param 加数。
* @return {@code this + val}.
*/
public BigInteger add(BigInteger val) {
if (val.signum == 0)
return this;
if (signum == 0)
return val;
if (val.signum == signum)//同号相加
return new BigInteger(add(mag, val.mag), signum);
//异号相减
int cmp = compareMagnitude(val);
if (cmp == 0)
return ZERO;//ZERO为值零的大型整数
int[] resultMag = (cmp > 0 ? subtract(mag, val.mag)
: subtract(val.mag, mag));//用大的数据减小的数据
resultMag = trustedStripLeadingZeroInts(resultMag);//规范化数据
return new BigInteger(resultMag, cmp == signum ? 1 : -1);//根据数据生成并返回
}
我们再来看其相关的各部分的实现:
/**
*将int数组x和y的内容相加。
*一个新的int数组来保存答案并返回其引用。
*/
private static int[] add(int[] x, int[] y) {
// 若x数组长度短于y数组, 则交换两个数组
if (x.length < y.length) {
int[] tmp = x;
x = y;
y = tmp;
}
int xIndex = x.length;
int yIndex = y.length;
int result[] = new int[xIndex];
long sum = 0;
/**
*这里采用位与将int转为long再相加并处理进位,进位保存在sum的高32位。
*/
if (yIndex == 1) {
sum = (x[--xIndex] & LONG_MASK) + (y[0] & LONG_MASK) ;//LONG_MASK为值0xffffffff的长整型
result[xIndex] = (int)sum;
} else {
//将公共部分相加
while (yIndex > 0) {
sum = (x[--xIndex] & LONG_MASK) +
(y[--yIndex] & LONG_MASK) + (sum >>> 32);
result[xIndex] = (int)sum;
}
}
// 进位传播
boolean carry = (sum >>> 32 != 0);
while (xIndex > 0 && carry)
carry = ((result[--xIndex] = x[xIndex] + 1) == 0);
//需要在进位传播后复制较长的数
while (xIndex > 0)
result[--xIndex] = x[xIndex];
//必要时扩充数组
if (carry) {
int bigger[] = new int[result.length + 1];
System.arraycopy(result, 0, bigger, 1, result.length);
bigger[0] = 0x01;
return bigger;
}
return result;
}
可以看见,add的实现与我们笔算的方式极其类似,我们称这种算法为循环带进位加法算法,简称加法。
/**
*比较该BigInteger的mag数组与指定的BigInteger的mag数组。这是compareTo忽略符号的版本。
* @param val BigInteger,它的mag数组需要比较。
* @return - 1,0或1,当该BigInteger的mag数组小于,等于或大于指定的BigInteger的mag数组。
*/
final int compareMagnitude(BigInteger val) {
int[] m1 = mag;
int len1 = m1.length;
int[] m2 = val.mag;
int len2 = m2.length;
//先比较数组长度
if (len1 < len2)
return -1;
if (len1 > len2)
return 1;
for (int i = 0; i < len1; i++) {//从高位依次比较
int a = m1[i];
int b = m2[i];
if (a != b)
return ((a & LONG_MASK) < (b & LONG_MASK)) ? -1 : 1;
}
return 0;
}
/**
* 从第一个int数组(大)减去第二个int数组(小)的内容。
* 第一个int数组(大)必须表示比第二个大的数。
* 这个方法分配了保存答案所需的空间。
*/
private static int[] subtract(int[] big, int[] little) {
int bigIndex = big.length;
int result[] = new int[bigIndex];
int littleIndex = little.length;
long difference = 0;
// 公共部分相减
while (littleIndex > 0) {
difference = (big[--bigIndex] & LONG_MASK) -
(little[--littleIndex] & LONG_MASK) +
(difference >> 32);
result[bigIndex] = (int)difference;
}
// 借位传播
boolean borrow = (difference >> 32 != 0);
while (bigIndex > 0 && borrow)
borrow = ((result[--bigIndex] = big[bigIndex] - 1) == -1);
// 需要在借位传播后复制较长的数
while (bigIndex > 0)
result[--bigIndex] = big[bigIndex];
return result;
}
可以看见,subtract的实现也与我们笔算的方式极其类似,我们称这种算法为循环带借位减法算法,简称减法。
/**
*返回去掉任何前置零的输入数组。
*由于来源是可信的,复制可能会被跳过。
*/
private static int[] trustedStripLeadingZeroInts(int val[]) {
int vlen = val.length;
int keep;
// 寻找非零索引
for (keep = 0; keep < vlen && val[keep] == 0; keep++)
;
return keep == 0 ? val : java.util.Arrays.copyOfRange(val, keep, vlen);
}
二、大数减法
/**
* 返回值为{@code (this - val)}的大型整数。
*
* @param 减数。
* @return {@code this - val}.
*/
public BigInteger subtract(BigInteger val) {
if (val.signum == 0)
return this;
if (signum == 0)
return val.negate();
if (val.signum != signum)//异号则加
return new BigInteger(add(mag, val.mag), signum);
//同号则减
int cmp = compareMagnitude(val);
if (cmp == 0)
return ZERO;
int[] resultMag = (cmp > 0 ? subtract(mag, val.mag)
: subtract(val.mag, mag));
resultMag = trustedStripLeadingZeroInts(resultMag);
return new BigInteger(resultMag, cmp == signum ? 1 : -1);
}
大数减法的处理同大数加法,只是对参数的符号处理不一样,因此其流程类似。