目录
大整数类的引入与声明
在C语言中,长度较长的数字通常使用高精度——也就是使用数组存储该长数字的每一位。C++中也可以使用类似的方法,但是STL和类(结构体)的存在可以使高精度使用更加方便:
C++中STL的存在可以使用不定长数组vector代替C中的定长数组,不需要考虑数字位数且不会浪费空间;
C++中类的存在可以在vector的基础上添加构造函数和其他成员函数(多用运算符重载函数)
所以可以定义以下的结构体BigInteger存储高精度的非负整数:
struct BigInteger{
//首先定义两个静态成员变量————规定vector中每一个元素可以存储的最大数据
static const int BASE = 100000000; //应用于使用数字赋值
static const int WIDTH = 8; //应用于使用字符串赋值
//接着定义vector
vector<int> s;
//构造函数
BigInteger(long long num=0){*this=num;}
//使用数字赋值的赋值运算符重载
BigInteger operator=(long long num)
{
s.clear() //赋值则先清除原有数据
do{
s.push_back(num%BASE); //这里保证了vector中存储的每个元素都在int范围内
num/=BASE;
}while(num>0);
}
//使用字符串赋值的运算符重载
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格式化录入,数据来源于字符串
sscanf(str.substr(start,end-start).c_ctr(),"%d",&x);
s.push_back(x);
}
return *this;
}
};
这段代码定义了两个静态成员变量,分别用于限制数字赋值和字符串赋值的情况下存储进vector的数字大小不超过int:
众所周知有符号的int型最大数据为2147483647(一共10位),所以取BASE为8位,且代码保证进入vector的数据都小于BASE,所以一定不会超限。
同理,取WIDTH为8保证了每一次只录入长度≤8的数据,小于int。
在字符串方式赋值中有一些比较重要的字符串处理函数,比如sscanf函数和sprintf函数:
sscanf和scanf的区别在于数据来源——scanf我们很熟悉,是以键盘为输入源;但是sscanf却是以字符串为输入源:
sscanf(const char* str1,"%d",&var); //表示从str1中读取数据并自动转为数字存储在变量var中
这里“自动转为数据”是一个很奇妙的过程。比如字符串是字符“1234”的组合,通过上述步骤var中得到的是真正int型1234。还有一种方法是使用字符串流 strstringstream。
同理sprintf(const char* str2,"%d",var)就是将数字var转成字符串形式,“输出”目标不是屏幕而是str2。
同时注意如果第一个参数是个string类型,不适用,具体操作后面会介绍。
其次是C++字符串类型自带的substr函数,可以写为str.substr(start,n),表示取出原字符串中下标从start开始的长度为n的子字符串,返回值同样是string。
那很容易就可以想到,可以将本函数和上面的sscanf结合起来,把子字符串作为输入源。但是由于string类型不能作为sscanf的第一个参数,所以引入函数string类型的库函数c_str(),写作str.c_str(),作用是将string类型的第一个元素值返回,达到的是一个将C++风格字符串转换为C风格的字符数组的效果。
此处还可以定义大整数类的流运算符重载,即通过cin>>x和cout<<x的方式进行输入输出:
//流输入运算符>>的重载
istream& operator>>(istream &in,BigInteger& x)
{
string s;
if(!(in>>s)) //确保有输入
return in;
x=s; //使用了赋值运算符的重载
return in;
}
//流输出运算符<<的重载
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]); //格式化输出,输出目标为buf
for(int j=0;j<strlen(buf);j++)
out<<buf[i];
}
return out;
}
这样一个大整数类的定义及赋值、输入输出运算符重载就算完成了。
大整数类的四则运算
以加法为例,核心步骤为 取余+进位:
//以下内容定义在结构体内部定义
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; //g为上一阶层保留下来的剩余值
if(i<s.size()) //加数1
x+=s[i];
if(i<b.s.size()) //加数2
x+=b.s[i];
c.s.push_back(x%BASE); //取余
g=x/BASE; //进位(留下待下一阶层使用)
}
根据+可以进一步定义+=:函数中调用+的重载实现
BigInteger operator+=(const BigInteger& b){
*this=*this+b;
return *this;
}
大整数类的比较
只需要定义一个小于,其他符号都可以通过小于的变化得到(类似于上面+=运用了+的性质)
//定义在结构体中
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; //相等
}
上面算法成立的前提是没有前导零(不然计算正确但是比较乱套)。那么同理可以通过小于运算符定义出其他所有运算符:
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);}