使用C++如何实现大数的加减(或乘除) ? 在开始之前 , 让我们来解释一下什么是大数 , 所谓的大数 , 就是那些超过内置类型所规定的长度的数 .比如说 , unsigned int型的变量范围是 : 0~4294967295 (到十亿位).
那么问题来了 , 如果想要计算百亿 ,千亿 ,甚至更高的数(超过内置类型所能表示的最大长度)怎么办 , 还能够依靠int型吗 .显然这个时候用给定好的类型是不能够完成的 , 那么我们就得思考怎么样来实现这样的大数运算 , 在C语言中 , 我们可以使用字符串来进行运算 , 那当然在C++中也是可以的.
但C++为我们提供了一个string类 , 用来更加方便的实现对字符串的操作 . 所以想要实现大数的运算 , string类是你最佳的选择 ;为什么这么说呢 ,因为 string类也属于容器的一种 , 它除了支持字符串的各种运算符的重载函数 , 还能够支持一些容器的操作.
前期准备
再开始说明算法实现之前 , 我们还需要一些前期的知识储备
- string类支持[ ]运算符的重载;
- string类型支持length() 得到字符串长度
- string类支持push_back() 和 insert();
- string类支持swap() 交换两个字符串;
- 在泛型算法中有reverse()函数 , 传入两个迭代器 , 使之逆置迭代器中的数据;
- #include< sstream >
stringstream temp; //名字自己起
可以实现string 与 数字 之间的转换 , 而且它还有一个优点 , 如果字符串表示一个负数(如 : “-250”) , 在转换为数字时 , 能够保留负号.
注 : 其实在大数运算的时候 , 直接使用 字符+(或-) '0’即可 , 因为编者是头一回见 , 所以在代码中使用stringstream来达到数字与string的转换.
如何把数字转换为string类型?
stringstream temp;
int a=123456;
string s;
temp<<a;
temp>>s;
cout<<"s : "<<s<<endl;
运行结果 :
如何把string类型转换为数字?
stringstream temp;
int a;
string s("-250");
temp<<s;
temp>>a;
cout<<"a :"<<a+1<<endl; //-249
运行结果 :
解题思路
再开始写代码之前 , 先要进行构思.
那么编者就来说说自己的思路 :
- 在加法中 , 根据计算习惯 , 将较大的数放在上面(使用swap()) .这里的较大指的是字符串的长度 , 长的一定大.如果两个字符串长度相同 , 则无需改变 ;
- 设置一个标志位 (flag), 用来考虑进位 ;
- 如果对位相加 >= 10(字符 => 数字) , 则进位(flag = true) , 再将相加结果(%10)存入记录结果的string中; 如果 < 10 ,则直接写入结果 ; 因为对位相加 , 最大也就 9 +9= 18 ,不会超过20 ;
- 在对位相加之前要判断之前的相加是否存在进位 , 如果有进位 , 则相加结果 +1 ;
- 还要考虑 , 当较短的字符串遍历完毕时 , 较长的那个是否也遍历完毕 , 如果便利完毕(a , b 等长) , 是否存在进位 ;如果还没有遍历完毕(a , b 不等长) , 也要考虑进位 ;
- 减法与之基本相似 , 大数在上 , 小数在下 ;唯一不同的是 , 加入了一个标志位(mark) , 用来记录计算结果的正负 ; 如果在开始时 , 两个数发生交换 , 则说明 , 相减的结果为负数(因为把较大的那个放在上边) ;
大数加减的框架
编者将框架放在这里 , 如有不同思路 , 可以互相交流
class BigInt
{
public:
BigInt(string str) :strDigit(str){}
private:
string strDigit; // 使用字符串存储大整数
};
// 打印函数
ostream& operator<<(ostream &out, const BigInt &src);
// 大数加法
BigInt operator+(const BigInt &lhs, const BigInt &rhs);
// 大数减法
BigInt operator-(const BigInt &lhs, const BigInt &rhs);
int main()
{
BigInt int1("28937697857832167849697653231243");
BigInt int2("9785645649886874535428765");
cout << int1 + int2 << endl;
//28937707643477817736572188660008
cout << int1 - int2 << endl;
//28937688072186517962823117802478
return 0;
}
实现代码
#include <iostream>
#include <vector> // 使用vector容器
#include <algorithm> // 使用泛型算法
#include <typeinfo>
#include <string>
#include<sstream>
using namespace std;
class BigInt
{
public:
BigInt(string str) :strDigit(str){}
private:
string strDigit; // 使用字符串存储大整数
//友元函数声明
friend BigInt operator+(const BigInt &lhs, const BigInt &rhs);
friend ostream& operator<<(ostream &out, const BigInt &src);
friend BigInt operator-(const BigInt &lhs, const BigInt &rhs);
};
//打印函数
ostream& operator<<(ostream &out, const BigInt &src)
{
out<<src.strDigit;
return out;
}
// 大数加法
BigInt operator+(const BigInt &lhs, const BigInt &rhs)
{
bool flag = false; //进位
stringstream tmpL; // 数字 字符 转换所需的中间变量
stringstream tmpR;
string tmp; //计算完成插入Fin 所需要的临时量
string Lhs = lhs.strDigit;
string Rhs =rhs.strDigit;
int lenL = Lhs.length();
int lenR = Rhs.length();
if(lenL < lenR)
{
Lhs.swap(Rhs);
int tmp = lenL;
lenL = lenR;
lenR = tmp;
}
string Fin ;
//Fin.resize(0); // sizeof(char) 最后会比计算结果长度多一个字节长度 Fin.resize(sizeof(0)); 丢失最后一个 , e = Fin.length()-1 会崩溃
int i = 0 , j = 0 , k = 0;
j = lenL-1;
int R , L ,sum = 0;
for(i = lenR-1 ; i >= 0; --i ,--j)
{
tmpR.clear(); //再重复使用时 , 要清空原有数据
tmpL.clear();
tmpR<<Rhs[i]; //字符 -> 数字
tmpL<<Lhs[j];
tmpR>>R;
tmpL>>L;
if(flag)
{
sum = L + R + 1;
}
else
{
sum = L + R;
}
if(sum >= 10)
{
flag = true;
sum %= 10;
tmpL.clear();
tmpL << sum; //数字 -> 字符
tmpL >>tmp;
Fin.insert(k++,tmp); // 使用尾插 Fin.insert(0,tmp); 采用头插的方式 , 效率低 push_back(sum + '0');
}
else
{
flag = false;
tmpL.clear();
tmpL << sum;
tmpL >>tmp;
Fin.insert(k++,tmp);
}
}
if(flag) //较短字符串的最高位 相加 有无进位
{
if(i == j) //如果两个数等长
{
tmpL.clear();
tmpL<<1;
tmpL>>tmp;
Fin.insert(k++,tmp);
//字符串反转
reverse(Fin.begin(),Fin.end());
return BigInt(Fin);
}
while(j >=0) //有进位则写入
{
tmpL.clear();
tmpL<<Lhs[j];
tmpL>>L;
sum = L + 1;
if(sum >= 10)
{
flag = true;
sum %= 10;
tmpL.clear();
tmpL << sum;
tmpL >>tmp;
Fin.insert(k++,tmp);
}
else
{
flag = false;
break;
}
--j;
}
tmpL.clear();
tmpL << sum;
tmpL >>tmp;
Fin.insert(k++,tmp);
--j;
}
while(j >= 0) //无进位 , 则摘抄
{
tmpL.clear();
tmpL<<Lhs[j--];
tmpL>>tmp;
Fin.insert(k++,tmp);
}
//字符串反转
reverse(Fin.begin(),Fin.end());
i = 0;
//结果全为零 , 则只显示一个
while(Fin[i++] == '0')
{ }
if((i - 1) == k)
{
return (string)"0";
}
return BigInt(Fin);
}
// 大数减法
BigInt operator-(const BigInt &lhs, const BigInt &rhs)
{
bool flag = false; //借位
bool mark = false; //最终结果的正负 false -> 正 ; true -> 负
stringstream tmpL; // 数字 字符 转换所需的中间变量
stringstream tmpR;
string tmp; //计算完成插入Fin 所需要的临时量
string Lhs = lhs.strDigit;
string Rhs =rhs.strDigit;
int lenL = Lhs.length();
int lenR = Rhs.length();
if(lenL < lenR)
{
Lhs.swap(Rhs);
int tmp = lenL;
lenL = lenR;
lenR = tmp;
mark = true;
}
string Fin ;
// Fin.resize(0);
int i = 0 , j = 0 , k = 0;
j = lenL-1;
int R , L ,dif = 0;
for(i = lenR-1 ; i >= 0; --i ,--j)
{
tmpR.clear(); //再重复使用时 , 要清空原有数据
tmpL.clear();
tmpR<<Rhs[i]; //字符 -> 数字
tmpL<<Lhs[j];
tmpR>>R;
tmpL>>L;
if(flag)
{
dif = L - R - 1;
}
else
{
dif = L - R;
}
if(dif < 0 )
{
flag = true;
dif += 10;
tmpL.clear();
tmpL<<dif;
tmpL>>tmp;
Fin.insert(k++,tmp);
}
else
{
flag = false;
tmpL.clear();
tmpL<<dif;
tmpL>>tmp;
Fin.insert(k++,tmp);
}
}
if(flag) //较短字符串的最高位 相加 有无借位
{
if(i == j) //如果两个数等长
{
tmpL.clear();
tmpL<<'-';
tmpL>>tmp;
Fin.insert(k++,tmp);
reverse(Fin.begin(),Fin.end());
return BigInt(Fin);
}
while(j >=0) //有借位则写入
{
tmpL.clear();
tmpL<<Lhs[j];
tmpL>>L;
dif = L - 1;
if(dif < 0)
{
flag = true;
dif += 10;
tmpL.clear();
tmpL << dif;
tmpL >>tmp;
Fin.insert(k++,tmp);
}
else
{
flag = false;
break;
}
--j;
}
tmpL.clear();
tmpL << dif;
tmpL >>tmp;
Fin.insert(k++,tmp);
--j;
}
while(j >= 0) //无借位 , 则摘抄
{
tmpL.clear();
tmpL<<Lhs[j--];
tmpL>>tmp;
Fin.insert(k++,tmp);
}
//判断最终的符号
if(mark)
{
tmpL.clear();
tmpL<<'-';
tmpL>>tmp;
Fin.insert(k++,tmp);
}
reverse(Fin.begin(),Fin.end());
i = 0;
//结果全为零 , 则只显示一个
while(Fin[i++] == '0')
{ }
if((i - 1) == k)
{
return (string)"0";
}
return BigInt(Fin);
}
int main()
{
BigInt int1("28937697857832167849697653231243");
BigInt int2("9785645649886874535428765");
BigInt int3("4785645649886874535428765");
BigInt int4("12222222222222222222222222");
BigInt int5("92222222222222222222222222");
BigInt int7("92222222222222222222222222");
cout << int2 + int1 << endl;
cout << int1 - int2 << endl;
cout << int4 + int5 << endl;
cout << int3 - int2 << endl;
cout << int2 - int1 << endl;
cout << int5 - int7 << endl;
return 0;
}
在代码中 , 在容器插入元素 , 可以使用insert()进行头插(效率太低) , 也可以使用push_banck()进行尾插(高效) ; 如果进行尾插的话 , 则要在最后进行字符串的逆置(泛型算法 reverse());而编者在写时 , 使用了insert() , 设置了一个 k 来记录插入位置(如果看过源码的话 , 不算析构和重新构造 , 其余代码insert和push_back是相同的,即 k 永远是最后元素的下一个位置 , 也算是尾插 );