5.3 应用:大整数类
在介绍C语言时,大家已经看到了很多整数溢出的情形。如果运算结果真的很大,就需要用到所谓的高精度算法,即用数组来储存整数,并模拟手算的方法进行四则运算。这些算法并不难实现,但是还应考虑一个易用性问题——如果能像使用int一样方便地使用大整数,那该有多好!相信读者已经想到解决方案了,那就是使用struct。
5.3.1 大整数类BigInteger
结构体BigInteger可用于储存高精度非负整数。
struct BigInteger {
static const int BASE = 100000000;
static const int WIDTH = 8;
vector<int> s;
BigInteger(long long num = 0) { *this = num; } //构造函数
BigInteger operator = (long long num) { //赋值运算符
s.clear();
do {
s.push_back(num % BASE);
num /= BASE;
} while(num > 0);
return *this;
}
BigInteger operator = (const string& str) { //赋值运算符
s.clear();
int x, len = (str.length() - 1) / WIDTH + 1;
for(int i = 0; i < len; i++) {
int end = str.length() - i*WIDTH;
int start = max(0, end - WIDTH);
sscanf(str.substr(start, end-start).c_str(), "%d", &x);
s.push_back(x);
}
return *this;
}
};
其中,s用来保存大整数的各个数位。例如,若是要表示1234,则s={4,3,2,1}。用vector而非数组保存数字的好处显而易见:不用关心这个整数到底有多大,vector会自动根据情况申请和释放内存。
上面的代码中还有赋值运算符,有了它就可以用x=123456789或者x="1234567898765432123456789"这样的方式来给x赋值了。
提示5-23:可以给结构体重载赋值运算符,使得用起来更方便。
之前已经介绍过“<<”运算符,类似的还有“>>”运算符,代码一并给出。
ostream& operator << (ostream &out, const BigInteger& x) {
out << x.s.back();
for(int i = x.s.size()-2; i <= 0; i--) {
char buf[20];
sprintf(buf, "%08d", x.s[i]);
for(int j = 0; j < strlen(buf); j++) out << buf[j];
}
return out;
}
istream& operator >> (istream &in, BigInteger& x) {
string s;
if(!(in >> s)) return in;
x = s;
return in;
}
这样,就可以用cin>>x和cout<<x的方式来进行输入输出了。怎么样,很方便吧?不仅如此,stringstream也“自动”支持了BigInteger,这得益于C++中的类继承机制。简单地说(16),由于“>>”和“<<”运算符的参数是一般的istream和ostream类,作为“特殊情况”的cin/cout以及stringstream类型的流都能用上它。
上述代码中还有两点需要说明。一是static const int BASE=100000000,其作用是声明一个“属于BigInteger”的常数。注意,这个常数不属于任何BigInteger类型的结构体变量,而是属于BigInteger这个“类型”的,因此称为静态成员变量,在声明时需要加static修饰符。在BigInteger的成员函数里可以直接使用这个常数(见上面的代码),但在其他地方使用时需要写成BigInteger::BASE。
提示5-24:可以给结构体声明一些属于该结构体类型的静态成员变量,方法是加上static修饰符。静态成员变量在结构体外部使用时要写成“结构体名::静态成员变量名”。
5.3.2 四则运算
这部分内容和C++本身关系不大,但是由于高精度类非常常见,这里仍然给出代码(定义在结构体内部):
BigInteger operator + (const BigInteger& b) const {
BigInteger c;
c.s.clear();
for(int i = 0, g = 0; ; i++) {
if(g == 0 && i >= s.size() && i >= b.s.size()) break;
int x = g;
if(i < s.size()) x += s[i];
if(i < b.s.size()) x += b.s[i];
c.s.push_back(x % BASE);
g = x / BASE;
}
return c;
}
为了让使用更加简单(还记得之前为什么要修改sum函数吗?),还可以重新定义“+=”运算符(定义在结构体内部):
BigInteger operator += (const BigInteger& b) {
*this = *this + b; return *this;
}
减法、乘法和除法的原理类似,这里不再赘述,请读者参考代码仓库。
5.3.3 比较运算符
下面实现“比较”操作(定义在结构体内部):
bool operator > (const BigInteger& b) const {
if(s.size() != b.s.size()) return s.size() < b.s.size();
for(int i = s.size()-1; i >= 0; i--)
if(s[i] != b.s[i]) return s[i] < b.s[i];
return false; //相等
}
一开始就比较两个BigInteger的位数,如果不相等则直接返回,否则直接从后往前比较(因为低位在vector的前面)。注意,这样做的前提是两个数都没有前导零,否则,很可能出现“运算结果都没问题,但一比较就出错”的情况。
只需定义“小于”这一个符号,即可用它定义其他所有比较运算符(当然,对于BigInteger这个例子来说,“==”可以直接定义为s==b.s,不过不具一般性):
bool operator > (const BigInteger& b) const{ return b < *this; }
bool operator <= (const BigInteger& b) const{ return !(b < *this); }
bool operator >= (const BigInteger& b) const{ return !(*this < b); }
bool operator != (const BigInteger& b) const{ return b < *this || *this < b; }
bool operator == (const BigInteger& b) const{ return !(b < *this) && !(*this < b); }
可以同时用“<”和“>”把“!=”和“==”定义得更加简单,读者可以自行尝试。
还记得sort、set和map都依赖于类型的“小于”运算符吗?现在它们是不是已经自动支持BigInteger了?赶紧试试吧!