一、解决问题:
实现长整数类BigInt, 支持任意精度的整数及其运算.
二、数据结构:
public class BigInt {
private int length;
private boolean pn; //positiveor negative. + is true, - is false
private String value;
}
其中字符串value为该大整数的绝对值,length存储该绝对值的位数,pn记录该大整数的符号。这样有利于解决正负数问题,所有大整数在操作时可以直接对value进行,最后统一考虑得到结果的符号。
三、具体方法:
1. 无参数构造函数BigInt()
BigInt() {
this.length = 1;
this.pn = true;
this.value = "0";
}
2. 带有字符串的构造函数BigInt(String s)
a) 通过私有方法isLegal(s)对参数进行合法性判断;
b) 判断该字符串是否带有符号,设置其符号pn;
c) 通过私有方法findStartPos(s)寻找字符串真正有意义的最高位;
d) 将最高位之后的所有字符存入value中,并在length中记录该value的长度;
e) 对0做特殊处理。
BigInt(String s) {
//assert isLegal(s);
if (isLegal(s)) {
if (s.charAt(0)=='+' || s.charAt(0)=='-') {
this.pn = s.charAt(0)=='+' ? true:false;
}
else {
this.length = s.length();
this.pn = true;
}
int sp = findStartPos(s);
this.value = s.substring(sp);
this.length = s.length()-sp;
if (this.value.charAt(0) == '0') {
this.pn = true;
this.length = 1;
this.value = "0";
}
}
else {
System.out.println("This BigInt is not legal!");
}
}
3. BigInt转字符串publicString toString()
由于数据结构中将符号位与数据位分离,所以在输出时只需要考虑在符号位pn==false时在value前加上负号即可。
public String toString() {
if (pn == false) {
return "-" + value;
}
return value;
}
4. 大整数比较是否相等public boolean equals(BigInt x)
与toString()类似,比较两个大整数的符号位和数据位是否相等即可。需要注意的是在数据位比较时要用equals(),直接使用等号比较的是引用的值而非真实数据。
public boolean equals(BigInt x) {
if (this.pn==x.pn && this.length==x.length && this.value.equals(x.value)) {
return true;
}
return false;
}
5. 大整数比较大小public int compare(BigInt x)
大于返回1,等于返回0,小于返回-1.
a) 直接通过符号位pn不同时比较两个数的大小;
b) 符号相同时,借助长度不同比较两个数的大小,正数长度大的大,负数长度小的大;
c) 符号长度都相同时,借助compareTo比较两个数的value,正数返回compareTo的结果对应点1和-1,负数返回compareTo结果的相反数的-1和1。
public int compare(BigInt x) {
if (this.pn != x.pn) {
return this.pn==true ? 1:-1;
}
if (this.length!=x.length) {
if (this.pn) {
return this.length>x.length ? 1:-1;
}
else {
return this.length<x.length ? 1:-1;
}
}
int tmp = this.value.compareTo(x.value);
if (this.pn) {
tmp *= -1;
}
if (tmp > 0) {
return -1;
}
if (tmp < 0) {
return 1;
}
return tmp;
}
6. 大整数加法public BigInt add(BigInt x)
对于加法操作,只考虑符号位相同的情况,将符号位不同的情况丢给减法。
a) 对0做特殊处理,返回另一个数,注意是创建一个新的大整数进行返回,如new BigInt(this.toString());
b) 如果两个数符号位不同,则创建x的备份xx,并使用私有方法changePN()改变xx的符号位,计算this-xx,将结果返回;
c) 创建两个字符串bigger和smaller,分别记录数据位较大的数据位和较小数的数据位;
d) 模拟加法操作:用bigger和smaller从最低位按位进行加法操作,借助私有方法charAdd()按位加,结果记录在字符串ans中;并通过carryValue来记录当前按位加结果是否得到进位,如果有进位,则在下一位加法操作时需要将carryValue加入按位加操作中,否则将其清零;
ans[i] =bigger[bigger.length-i]+smaller[smaller.length-i]+carryValue
e) 当smaller的最高位加完后,需要对bigger多出的高位继续与carryValue进行按位加;
f) 判断最高位是否产生进位,如果产生则补充一个最高位1;
g) 将ans进行反转操作,用其构造大整数,并将其符号位置为this的符号位。
public BigInt add(BigInt x) {
if (x.value.equals("0")) {
return new BigInt(this.toString());
}
if (this.value.equals("0")) {
return new BigInt(x.toString());
}
if (this.pn != x.pn) {
BigInt xx = new BigInt(x.toString());
xx.changePN();
return this.substract(xx);
}
String bigger, smaller;
if ((this.compare(x)>=0&&this.pn)||(this.compare(x)<0&&!this.pn)) {
bigger = new String(this.value);
smaller = new String(x.value);
}
else {
bigger = new String(x.value);
smaller = new String(this.value);
}
String tmpAns = new String();
int carryValue = 0;
for (int i = 1; i <= smaller.length(); i++) {
int tmpAdd = charAdd(bigger.charAt(bigger.length()-i), smaller.charAt(smaller.length()-i)) + carryValue;
carryValue = tmpAdd>=10 ? 1:0;
tmpAns += String.valueOf(tmpAdd-carryValue*10);
}
int pos = bigger.length()-smaller.length()-1;
while(carryValue!=0 && pos>=0) {
int tmpAdd = charAdd(bigger.charAt(pos), '1');
carryValue = tmpAdd>=10 ? 1:0;
tmpAns += String.valueOf(tmpAdd-carryValue*10);
pos--;
}
String ans = new String();
tmpAns = reverse(tmpAns);
if (carryValue == 0) {
ans = bigger.substring(0, Math.max(pos+1, 0))+tmpAns;
}
else if (pos < 0) {
ans = "1"+tmpAns;
}
if (!this.pn) {
ans = "-"+ans;
}
return new BigInt(ans);
}
7. 大整数减法操作public BigInt substract(BigInt x)
类似加法,只考虑符号位相同的情况,将符号位不同的情况丢给加法;接下来只介绍模拟减法的操作。
tmpSub= charSub(bigger[bigger.length()-i], smaller[smaller.length()-i], carryValue);
public BigInt substract(BigInt x) {
if (x.value.equals("0")) {
return new BigInt(this.toString());
}
else if (this.value.equals("0")) {
BigInt ans = new BigInt(x.toString());
ans.changePN();
return ans;
}
if (this.pn != x.pn) {
BigInt xx = new BigInt(x.toString());
xx.changePN();
return this.add(xx);
}
String bigger, smaller;
boolean tag = this.compare(x)>=0 ? true:false;
if ((this.compare(x)>=0&&this.pn)||(this.compare(x)<=0&&!this.pn)) {
bigger = new String(this.value);
smaller = new String(x.value);
}
else {
bigger = new String(x.value);
smaller = new String(this.value);
}
String tmpAns = new String();
int carryValue = 0;
for (int i = 1; i <= smaller.length(); i++) {
int tmpAdd = charSub(bigger.charAt(bigger.length()-i), smaller.charAt(smaller.length()-i), carryValue);
carryValue = tmpAdd<0 ? 1:0;
if (tmpAdd == -100) {
carryValue = 1;
tmpAdd = 0;
}
tmpAns += String.valueOf(Math.abs(tmpAdd));
}
int pos = bigger.length()-smaller.length()-1;
while(carryValue!=0 && pos>=0) {
int tmpAdd = charSub(bigger.charAt(pos), '0', carryValue);
carryValue = tmpAdd<0 ? 1:0;
tmpAns += String.valueOf(Math.abs(tmpAdd));
pos--;
}
String ansStr = new String();
tmpAns = reverse(tmpAns);
if (carryValue == 0) {
ansStr = bigger.substring(0, Math.max(pos+1, 0))+tmpAns;
}
BigInt ans = new BigInt(ansStr);
ans.pn = tag;
return ans;
}
其中私有方法charSub():
private int charSub(char x, char y, int c) {
int ans = x + 10 - c - y;
if (ans < 10) {
if (ans == 0) {
ans = 100;
}
return ans*(-1); // there is a negative zero problem
}
return ans-10;
}
为了解决参数为0, 9,1这样的负0问题,将该结果置为-100返回做特殊处理。
8. 大整数乘法操作public BigInt multiply(BigInt x)
采用分治的思想,设两个数分别为M1, M2,长度为均为l。可将其表示为:
M1 = AB,A为前l/2长度的数据位,B为后l-l/2长度的数据位;
M2 = CD,C为前l/2长度的数据位,D为后l-l/2长度的数据位;
于是M1*M2可表示为
M1*M2 = A*C*(10^((l-l/2)*2)) + B*D + ((A-B)*(D-C)+A*C+B*D) *(10^(l-l/2))
但是无法保证两个数数据位长度均为l,故需要将数据位短的前面补零补齐l位。之所以采取这样的方法是因为原本的乘法操作分解之后仍为四次乘法,而采取上式得乘法操作分解之后只需三次乘法操作,时间复杂度降为O(l^log(3))。
public BigInt multiply(BigInt x) {
if (x.value.equals("0") || this.value.equals("0")) {
return new BigInt();
}
boolean ansPN = !(this.pn ^ x.pn);
if (x.length == 1 && this.length == 1) {
int ians = Integer.parseInt(x.value)*Integer.parseInt(this.value);
BigInt ans = new BigInt(String.valueOf(ians));
ans.setPN(ansPN);
return ans;
}
String a = this.value, b = x.value;
/**********Unify Length**********/
int len1 = Math.max(a.length(), b.length())-a.length();
int len2 = Math.max(a.length(), b.length())-b.length();
for (int i = 0; i < len1; i++) {
a = "0" + a;
}
for (int i = 0; i < len2; i++) {
b = "0" + b;
}
BigInt a1 = new BigInt(a.substring(0, a.length()/2));
BigInt a2 = new BigInt(a.substring(a.length()/2));
BigInt b1 = new BigInt(b.substring(0, b.length()/2));
BigInt b2 = new BigInt(b.substring(b.length()/2));
BigInt ans1 = a1.multiply(b1);//a1*b1
BigInt ans2 = a2.multiply(b2);//a2*b2
BigInt sub1 = a1.substract(a2);
BigInt sub2 = b2.substract(b1);
BigInt ans3 = sub1.multiply(sub2);//(a1-a2)(b2-b1)
ans3 = ans3.add(ans1);
ans3 = ans3.add(ans2);
String tmp = ans1.value;
for (int i = 0; i < (a.length()-a.length()/2)*2; i++) {
tmp += "0";
}
ans1 = new BigInt(tmp);
tmp = ans3.value;
for (int i = 0; i < (a.length()-a.length()/2); i++) {
tmp += "0";
}
if (!ans3.pn) {
tmp = "-"+tmp;
}
ans3 = new BigInt(tmp);
BigInt ans = ans1.add(ans3).add(ans2);
ans.setPN(ansPN);
return ans;
}
9. 大整数除法操作public BigInt divide(BigInt x)(除数非0)
a) 判断除数的数据位是否大于被除数的数据位,如果大于,返回0;
b) 在x后补零,使得x变为不大于this的最大补零数,记补了cnt个0,为方便操作,相当于x*addZero,addZero=100…00,其中有cnt个0;
c) 初始化商quotient为0;
d) LOOP1:循环cnt次;
e) LOOP2:每次计算this-x,this = this-x,quotient = quotient+addZero,直到this<x;
f) 将addZero和x末尾去掉一个零,转LOOP1;
g) 将商的符号位置位为this和x的符号位的异或。
public BigInt divide(BigInt x) {
boolean ansPN = !(this.pn ^ x.pn);
if (x.toString().equals("0")) {
System.out.println("The divider cannot be 0!");
BigInt ans = new BigInt("1");
ans.setPN(ansPN);
return ans;
}
if (unsignedCompare(x) < 0) {
return new BigInt("0");
}
String a = this.value, b = x.value, addZero = new String("1");
int cnt = a.length()-b.length();
for (int i = 0; i < cnt; i++) {
b += "0";
addZero += "0";
}
BigInt divA = new BigInt(a), divB = new BigInt(b);
BigInt quotien = new BigInt("0");
while(cnt >= 0) {
BigInt addBI = new BigInt(addZero);
while(divA.compare(divB) >= 0) {
quotien = quotien.add(addBI);
divA = divA.substract(divB);
}
divB = new BigInt(divB.value.substring(0, Math.max(1,divB.value.length()-1)));
addZero = addZero.substring(0, Math.max(1,cnt));
cnt--;
}
quotien.setPN(ansPN);
return quotien;
}
10. 大整数取余操作public BigInt mod(BigInt x)
a) 调用this.divid(x)得到商q;
b) 调用q.multiply(x)得到积tmp;
c) 返回this.substract(tmp).
public BigInt mod(BigInt x) {
BigInt tmp = this.divide(x);
tmp = tmp.multiply(x);
return this.substract(tmp);
}
11. 阶乘操作public static BigInt factorial(BigInt x):
递归调用x.multiply(factorial(x.substract(ONE)))即可;
public BigInt factorial(BigInt x) {
if (!x.equals(ZERO)) {
return x.multiply(factorial(x.substract(ONE)));
}
return new BigInt("1");
}
12. 指数函数public static BigInt expo(BigInt x):
递归调用TWO.multiply(expo(x.substract(ONE)))即可;
public BigInt expo(BigInt x) {
if (!x.equals(ZERO)) {
return TWO.multiply(expo(x.substract(ONE)));
}
return new BigInt("1");
}
常量:
public static final BigInt ZERO = new BigInt();
public static final BigInt ONE = new BigInt("1");
public static final BigInt TWO = new BigInt("2");
private int unsignedCompare(BigInt x) {//无符号比较
BigInt a = new BigInt(this.value);
BigInt b = new BigInt(x.value);
return a.compare(b);
}
private String reverse(String s) {//字符串反转
StringBuffer sb = new StringBuffer(s);
return sb.reverse().toString();
}
private int charAdd(char x, char y) {//字符加
return x + y - '0' - '0';
}
private int charSub(char x, char y, int c) {//字符减
int ans = x + 10 - c - y;
if (ans < 10) {
if (ans == 0) {
ans = 100;
}
return ans*(-1); // there is a negative zero problem
}
return ans-10;
}
private void changePN() {//符号位反转
this.pn = this.pn==true ? false:true;
if (this.value.charAt(0) == '0') {
this.pn = true;
}
}
private void setPN(boolean targetPN) {//设置符号位
this.pn = targetPN;
}
private int findStartPos(String s) {//寻找字符串规范起始位置
for (int i = 0; i < s.length(); i++) {
if (Character.isDigit(s.charAt(i)) && s.charAt(i)!='0') {
return i;
}
}
return s.length()-1;//this is a zero!
}
private boolean isLegal(String s) {//构造函数中判断字符串是否合法
if (s == null || ((s.length()==1)&&(!Character.isDigit(s.charAt(0)))) || ((s.length()>1)&&(s.charAt(0)!='+')&&(s.charAt(0)!='-')&&(!Character.isDigit(s.charAt(0))))) {
return false;
}
for (int i = 1; i < s.length(); i++) {
if (!Character.isDigit(s.charAt(i))) {
return false;
}
}
return true;
}