大数的四则运算
C++对于数字的表示是有限制的,不像Python那样对数字范围没有限制,也不像Java那样有现成的大数类。因此,手写大数运算成为了经常出现的考察点。忙里偷闲,自己尝试使用string对象实现大数的加、减、乘、除四则运算。其中,加减运算仅限非负整数的运算,因为负数的加减运算可以等价为某种形式的加法或者减法运算,故不做考虑;乘除运算只考虑大整数运算,不考虑小数的计算,但是除法的计算结果精确到6位小数(最后一位采用直接舍去的方法获得)。
说明:此实现仅对常规计算做了测试,某些特殊情况可能没有考虑到,如果发现有计算bug,请在评论区反馈,谢谢。
前置处理
判断两个大数的大小
这里仅判断小于的情况:
bool Lower(string str1, string str2){ //长度长的一定大(这里假设除了0以外,都没有前导0),长度相等则字典序小的数字更小
return str1.size()<str2.size()||(str1.size()==str2.size()&&str1<str2);
}
判断是否为0
这里认为全部为0的数字就是0,比如00000:
bool CheckZero(const string &str){ //检查是否等于0
int size=str.size(); //如果全是0则为0,这里假设不会有带符号的+00000或者-00000作为输入
for(int i=0;i<size;i++)
if(str[i]!='0') return false;
return true;
}
判断是否为负
只需要判断第一位是不是负号即可,这里不考虑正号的存在,即认为不使用正号:
bool CheckNegative(const string &str){ //检查是否为负数
return str[0]=='-';
}
大数加法
实现方法
大数加法的实现,模仿了我们列竖式的计算方法,从个位开始,一位一位的相加,每次考虑进位即可。由于读入的字符串0号位置为最高位,所以我们采用逆序的办法访问字符串,然后每位计算即可。同样的,因为计算时候得到的每一位都是逆序的,最后的结果要进行逆置。
加法,可能导致最终位数多一位(最高位的进位),所以要记得处理。
最后,我们来考虑一些特殊情况,比如两个数字都是0,或者其中有一个是0,我们就可以快速得到结果,省去了遍历过程。
实现代码
string Add(string str1, string str2){
//关于0的处理
if(CheckZero(str1)&&CheckZero(str2)) return "0"; //如果都是0
else if(CheckZero(str1)) return str2; //如果有个一为0
else if(CheckZero(str2)) return str1;
string ans;
int i=str1.size()-1,j=str2.size()-1;
int flag=0; //进位标记
while(i>=0||j>=0||flag){
int numa=i>=0?str1[i--]-'0':0; //如果所有位都访问完毕,对应的位置用0代替
int numb=j>=0?str2[j--]-'0':0;
flag=numa+numb+flag; //计算两位的和
ans+='0'+flag%10; //取个位保存在答案中
flag/=10; //计算进位
}
reverse(ans.begin(),ans.end());
return ans;
}
大数减法
实现方法
大数减法的实现,也模仿了我们列竖式的计算方法。从个位起,每次计算一位,首先根据后一位是否借位,先减去借位,然后判断当前位是否够减,如果需要借位,则向前一位借位后在减,直到运算完毕。同样的,因为我们要从个位开始计算,所以计算得到的结果必然是逆序的,最终要记得将结果逆置。
减法,可能出现前导0,要记得清除前导零。
最后,我们来考虑一些特殊情况,比如两个数相同或者有一个数字为0,我们可以直接得到结果,从而避免了复杂的处理过程。
实现代码
string Sub(string str1, string str2){
//处理0的情况
if(str1==str2||(CheckZero(str1)&&CheckZero(str2))) return "0"; //如果两数相等或者都是0
else if(CheckZero(str1)) return "-"+str2; //如果第一个数字为0
else if(CheckZero(str2)) return str1; //如果第二个数字为0
//定正负
int negative=0; //结果的正负号
if(Lower(str1,str2)){
swap(str1,str2); //保证str1大于str2
negative=1; //如果str1小于str2,则结果过为负值
}
string ans;
int i=str1.size()-1,j=str2.size()-1;//逆序开始处理
int flag=0; //借位标记
while(i>=0||j>=0){
int numa=i>=0?str1[i--]-'0':0; //取每一位,因为长度可能不同所以当某一个已经读取完毕时对应位置取0
int numb=j>=0?str2[j--]-'0':0;
numa-=flag; //先减去借位
if(numa<numb){ //如果不够减则向上一位借位(只可能借一位)
numa+=10; //借位并记录借位
flag=1;
}
else flag=0; //如果不借位,则借位标记为0
ans+='0'+numa-numb; //计算当前位置并保存
}
i=ans.size()-1;
while(ans[i]=='0') i--;
ans=ans.substr(0,i+1); //去除前导0,如111-110=1
if(negative) ans+='-'; //如果计算结果是负数,添加负数符号
reverse(ans.begin(),ans.end()); //因为是逆序计算得到的结果,所以需要翻转一下
return ans;
}
大数乘法
实现方法
大数乘法的实现,还采用我们竖式计算的方法。从个位开始,每次计算被乘数和乘数一位的积,然后借助我们写好的大数加法实现最终结果的累加。但是大数乘法需要考虑正负的问题,所以需要对正负号进行处理,对两个数的符号使用异或最终可以确定乘积结果的正负。
乘法,因为乘数的每一位都有相应的权值(个十百千万),因此我们对于乘数每一位的积进行运算时要考虑该位置的权值,在积的后边补充相应个数的零即可。
最后,我们考虑一些特殊情况,比如两个数字中只要有一个是0,则结果就是0。
实现代码
string Mul(string str1, string str2){
if(CheckZero(str1)||CheckZero(str2)) return "0"; //如果有一个为0,则结果为0
int negative=0,negastr1=0,negastr2=0; //定正负
if(CheckNegative(str1)){ //确定正负号标记,并且去掉-字符
negastr1=1; str1=str1.substr(1,str1.size()-1);
}
if(CheckNegative(str2)){
negastr2=1; str2=str2.substr(1,str2.size()-1);
}
negative=negastr1^negastr2; //异或运算确定结果的正负号
string ans;
if(Lower(str1,str2)) swap(str1,str2); //保证str1大于等于str2
int size1=str1.size(),size2=str2.size();
for(int i=size2-1;i>=0;i--){ //遍历较小数字的每一位
string temp(size2-1-i,'0'); //temp为str1乘以str2[i]的积,根据str2[i]的权重(个十百千万,补充对应个数的0)
int flag=0; //进位标记
for(int j=size1-1;j>=0;j--){ //temp
flag+=(str1[j]-'0')*(str2[i]-'0');
temp.push_back('0'+(flag%10));
flag/=10;
}
if(flag) temp.push_back('0'+flag); //如果最高位还有进位
reverse(temp.begin(),temp.end());
ans=Add(ans,temp); //将计算结果累加到最终的结果上
}
if(negative) ans="-"+ans; //处理结果的正负号
return ans;
}
大数除法
实现方法
大数除法的实现,同样采用我们除法式子的方法进行计算。首先,使用异或的方法确定结果的正负号。两个数字相除的时候,如果第一个数字大于等于第二个数字,则结果一定是大于等于1的,否则小于1。于是,为了实现小于1的结果表示,我们为结果精确到小数点后6位。这里采用的方法为:事先确定是否为纯小数,然后在第一个数字的末尾加上6个0,然后使用我们除法式子的方法进行计算。从第一个数字的头部开始,找到第一个长度能够进行商运算的数字开始,计算商并将临时的余数补足一位进行下一位商的计算,直到计算完毕。
除法,可能遇到除数为0的情况,因此需要在计算的时候提前进行判定。另外计算过程中,计算商的方法使用大数的减法操作,因此可能会遇到0堆积的情况(长度增大),会影响到大小的比较判定,要注意处理。
最后,我们考虑一些特殊情况,比如被除数为0的时候,可以直接输出结果0.000000。
实现代码
string Div(string str1, string str2){
//处理除数为0的情况和被除数为0的情况
if(CheckZero(str2)) return "The divisor cannot be zero!";
else if(CheckZero(str1)) return "0.000000";
int negative=0,negastr1=0,negastr2=0; //定正负
if(CheckNegative(str1)){ //确定正负号标记,并且去掉-
negastr1=1; str1=str1.substr(1,str1.size()-1);
}
if(CheckNegative(str2)){
negastr2=1; str2=str2.substr(1,str2.size()-1);
}
negative=negastr1^negastr2; //异或运算确定结果的正负号
int point=0; //结果是否为纯小数
if(Lower(str1,str2)) point=1; //如果str1小于str2,则计算为纯小数
string ans; //计算结果
str1+=string(6,'0'); //补足6个0,用于计算小数位
int size1=str1.size(),size2=str2.size();
int i=size2-1; //商第一位的位置
string temp=str1.substr(0,i); //从str1上取size2-1个字符
for(i;i<size1;i++){
temp+=str1[i]; //从后边拿出一位,预先处理可以防止结尾处越界
int cnt=0; //当前位的商,也就是temp中包含了多少个str2,使用减法 //如果temp不为0,则计算商
while(Lower(str2,temp)||temp==str2){ //如果当前位商不为0,则计算商
temp=Sub(temp,str2);
cnt++;
}
if(temp=="0") temp.clear(); //如果某次计算结果为0,则清空,避免0的堆积,比如111000 111
ans.push_back('0'+cnt); //保存商
}
i=0;
while(ans[i]=='0') i++;
ans=ans.substr(i,ans.size()-i); //去除前导0
if(point){ //如果是纯小数,补足6位并添加小数点
int len=6-ans.size();
ans="0."+string(len,'0')+ans;
}
else ans.insert((ans.end()-6),'.'); //如果不是小数,则只需要插入小数点
if(negative) ans="-"+ans; //最后一步骤,如果是负数带上负号
return ans;
}
其他内容
这里增加一些其他的提示输出,以添加一些基本的交互操作,实现如下:
void ShowMenu(){
cout<<"请选择要进行的大数运算:\n"
<<"a) 加法 b) 减法\n"
<<"c) 乘法 d) 除法\n"
<<"q) 退出\n"
<<"请输入你的选择: ";
}
void ShowMenu(char choice){
cout<<"请输入要计算的两个数字";
switch(choice){
case 'a':cout<<"(仅支持非负整数加法计算): "<<endl;break;
case 'b':cout<<"(仅支持非负整数减法计算): "<<endl;break;
case 'c':cout<<"(仅支持整数乘法计算): "<<endl;break;
case 'd':cout<<"(仅支持整数除法计算,计算结果显示6位小数): "<<endl;break;
}
}
效果演示
完整代码
完整代码如下所示:
#include<bits/stdc++.h>
using namespace std;
//大数四则运算,两个参数都不能为空
string Add(string str1, string str2); //大数加法
string Sub(string str1, string str2); //大数减法
string Mul(string str1, string str2); //大数乘法
string Div(string str1, string str2); //大数除法
bool Lower(string str1, string str2); //大数比较(小于)
bool CheckZero(const string &str); //检查是不是0,比如0000认为是0
bool CheckNegative(const string &str); //检查是不是负数
void ShowMenu(); //提示菜单
void ShowMenu(char choice); //二级菜单
int main(){
string a,b;
char ch;
ShowMenu();
while(cin>>ch&&ch!='q'){ //循环打印菜单并提示用户输入
ShowMenu(ch);
cin>>a>>b;
switch(ch){
case 'a':cout<<a<<" + "<<b<<" = "<<Add(a,b)<<endl;break;
case 'b':cout<<a<<" - "<<b<<" = "<<Sub(a,b)<<endl;break;
case 'c':cout<<a<<" * "<<b<<" = "<<Mul(a,b)<<endl;break;
case 'd':cout<<a<<" / "<<b<<" = "<<Div(a,b)<<endl;break;
}
ShowMenu();
}
return 0;
}
string Add(string str1, string str2){
//关于0的处理
if(CheckZero(str1)&&CheckZero(str2)) return "0"; //如果都是0
else if(CheckZero(str1)) return str2; //如果有个一为0
else if(CheckZero(str2)) return str1;
string ans;
int i=str1.size()-1,j=str2.size()-1;
int flag=0; //进位标记
while(i>=0||j>=0||flag){
int numa=i>=0?str1[i--]-'0':0; //如果所有位都访问完毕,对应的位置用0代替
int numb=j>=0?str2[j--]-'0':0;
flag=numa+numb+flag; //计算两位的和
ans+='0'+flag%10; //取个位保存在答案中
flag/=10; //计算进位
}
reverse(ans.begin(),ans.end());
return ans;
}
string Sub(string str1, string str2){
//处理0的情况
if(str1==str2||(CheckZero(str1)&&CheckZero(str2))) return "0"; //如果两数相等或者都是0
else if(CheckZero(str1)) return "-"+str2; //如果第一个数字为0
else if(CheckZero(str2)) return str1; //如果第二个数字为0
//定正负
int negative=0; //结果的正负号
if(Lower(str1,str2)){
swap(str1,str2); //保证str1大于str2
negative=1; //如果str1小于str2,则结果过为负值
}
string ans;
int i=str1.size()-1,j=str2.size()-1;//逆序开始处理
int flag=0; //借位标记
while(i>=0||j>=0){
int numa=i>=0?str1[i--]-'0':0; //取每一位,因为长度可能不同所以当某一个已经读取完毕时对应位置取0
int numb=j>=0?str2[j--]-'0':0;
numa-=flag; //先减去借位
if(numa<numb){ //如果不够减则向上一位借位(只可能借一位)
numa+=10; //借位并记录借位
flag=1;
}
else flag=0; //如果不借位,则借位标记为0
ans+='0'+numa-numb; //计算当前位置并保存
}
i=ans.size()-1;
while(ans[i]=='0') i--;
ans=ans.substr(0,i+1); //去除前导0,如111-110=1
if(negative) ans+='-'; //如果计算结果是负数,添加负数符号
reverse(ans.begin(),ans.end()); //因为是逆序计算得到的结果,所以需要翻转一下
return ans;
}
string Mul(string str1, string str2){
if(CheckZero(str1)||CheckZero(str2)) return "0"; //如果有一个为0,则结果为0
int negative=0,negastr1=0,negastr2=0; //定正负
if(CheckNegative(str1)){ //确定正负号标记,并且去掉-字符
negastr1=1; str1=str1.substr(1,str1.size()-1);
}
if(CheckNegative(str2)){
negastr2=1; str2=str2.substr(1,str2.size()-1);
}
negative=negastr1^negastr2; //异或运算确定结果的正负号
string ans;
if(Lower(str1,str2)) swap(str1,str2); //保证str1大于等于str2
int size1=str1.size(),size2=str2.size();
for(int i=size2-1;i>=0;i--){ //遍历较小数字的每一位
string temp(size2-1-i,'0'); //temp为str1乘以str2[i]的积,根据str2[i]的权重(个十百千万,补充对应个数的0)
int flag=0; //进位标记
for(int j=size1-1;j>=0;j--){ //temp
flag+=(str1[j]-'0')*(str2[i]-'0');
temp.push_back('0'+(flag%10));
flag/=10;
}
if(flag) temp.push_back('0'+flag); //如果最高位还有进位
reverse(temp.begin(),temp.end());
ans=Add(ans,temp); //将计算结果累加到最终的结果上
}
if(negative) ans="-"+ans; //处理结果的正负号
return ans;
}
string Div(string str1, string str2){
//处理除数为0的情况和被除数为0的情况
if(CheckZero(str2)) return "The divisor cannot be zero!";
else if(CheckZero(str1)) return "0.000000";
int negative=0,negastr1=0,negastr2=0; //定正负
if(CheckNegative(str1)){ //确定正负号标记,并且去掉-
negastr1=1; str1=str1.substr(1,str1.size()-1);
}
if(CheckNegative(str2)){
negastr2=1; str2=str2.substr(1,str2.size()-1);
}
negative=negastr1^negastr2; //异或运算确定结果的正负号
int point=0; //结果是否为纯小数
if(Lower(str1,str2)) point=1; //如果str1小于str2,则计算为纯小数
string ans; //计算结果
str1+=string(6,'0'); //补足6个0,用于计算小数位
int size1=str1.size(),size2=str2.size();
int i=size2-1; //商第一位的位置
string temp=str1.substr(0,i); //从str1上取size2-1个字符
for(i;i<size1;i++){
temp+=str1[i]; //从后边拿出一位,预先处理可以防止结尾处越界
int cnt=0; //当前位的商,也就是temp中包含了多少个str2,使用减法 //如果temp不为0,则计算商
while(Lower(str2,temp)||temp==str2){ //如果当前位商不为0,则计算商
temp=Sub(temp,str2);
cnt++;
}
if(temp=="0") temp.clear(); //如果某次计算结果为0,则清空,避免0的堆积,比如111000 111
ans.push_back('0'+cnt); //保存商
}
i=0;
while(ans[i]=='0') i++;
ans=ans.substr(i,ans.size()-i); //去除前导0
if(point){ //如果是纯小数,补足6位并添加小数点
int len=6-ans.size();
ans="0."+string(len,'0')+ans;
}
else ans.insert((ans.end()-6),'.'); //如果不是小数,则只需要插入小数点
if(negative) ans="-"+ans; //最后一步骤,如果是负数带上负号
return ans;
}
bool Lower(string str1, string str2){ //长度长的一定大(这里假设除了0以外,都没有前导0),长度相等则字典序小的数字更小
return str1.size()<str2.size()||(str1.size()==str2.size()&&str1<str2);
}
bool CheckZero(const string &str){ //检查是否等于0
int size=str.size(); //如果全是0则为0,这里假设不会有带符号的+00000或者-00000作为输入
for(int i=0;i<size;i++)
if(str[i]!='0') return false;
return true;
}
bool CheckNegative(const string &str){ //检查是否为负数
return str[0]=='-';
}
void ShowMenu(){
cout<<"请选择要进行的大数运算:\n"
<<"a) 加法 b) 减法\n"
<<"c) 乘法 d) 除法\n"
<<"q) 退出\n"
<<"请输入你的选择: ";
}
void ShowMenu(char choice){
cout<<"请输入要计算的两个数字";
switch(choice){
case 'a':cout<<"(仅支持非负整数加法计算): "<<endl;break;
case 'b':cout<<"(仅支持非负整数减法计算): "<<endl;break;
case 'c':cout<<"(仅支持整数乘法计算): "<<endl;break;
case 'd':cout<<"(仅支持整数除法计算,计算结果显示6位小数): "<<endl;break;
}
}
演示效果
运行程序,输入输出效果如下所示:
请选择要进行的大数运算:
a) 加法 b) 减法
c) 乘法 d) 除法
q) 退出
请输入你的选择: a
请输入要计算的两个数字(仅支持非负整数加法计算):
123456789 987654321
123456789 + 987654321 = 1111111110
请选择要进行的大数运算:
a) 加法 b) 减法
c) 乘法 d) 除法
q) 退出
请输入你的选择: b
请输入要计算的两个数字(仅支持非负整数减法计算):
123456789 987654321
123456789 - 987654321 = -864197532
请选择要进行的大数运算:
a) 加法 b) 减法
c) 乘法 d) 除法
q) 退出
请输入你的选择: b
请输入要计算的两个数字(仅支持非负整数减法计算):
12345 12345
12345 - 12345 = 0
请选择要进行的大数运算:
a) 加法 b) 减法
c) 乘法 d) 除法
q) 退出
请输入你的选择: b
请输入要计算的两个数字(仅支持非负整数减法计算):
123456 456
123456 - 456 = 123000
请选择要进行的大数运算:
a) 加法 b) 减法
c) 乘法 d) 除法
q) 退出
请输入你的选择: c
请输入要计算的两个数字(仅支持整数乘法计算):
12345 000
12345 * 000 = 0
请选择要进行的大数运算:
a) 加法 b) 减法
c) 乘法 d) 除法
q) 退出
请输入你的选择: c
请输入要计算的两个数字(仅支持整数乘法计算):
123 -456789
123 * -456789 = -56185047
请选择要进行的大数运算:
a) 加法 b) 减法
c) 乘法 d) 除法
q) 退出
请输入你的选择: c
请输入要计算的两个数字(仅支持整数乘法计算):
-123 -456789
-123 * -456789 = 56185047
请选择要进行的大数运算:
a) 加法 b) 减法
c) 乘法 d) 除法
q) 退出
请输入你的选择: d
请输入要计算的两个数字(仅支持整数除法计算,计算结果显示6位小数):
123 456
123 / 456 = 0.269736
请选择要进行的大数运算:
a) 加法 b) 减法
c) 乘法 d) 除法
q) 退出
请输入你的选择: d
请输入要计算的两个数字(仅支持整数除法计算,计算结果显示6位小数):
456789 123456
456789 / 123456 = 3.700014
请选择要进行的大数运算:
a) 加法 b) 减法
c) 乘法 d) 除法
q) 退出
请输入你的选择: d
请输入要计算的两个数字(仅支持整数除法计算,计算结果显示6位小数):
123 000
123 / 000 = The divisor cannot be zero!
请选择要进行的大数运算:
a) 加法 b) 减法
c) 乘法 d) 除法
q) 退出
请输入你的选择: d
请输入要计算的两个数字(仅支持整数除法计算,计算结果显示6位小数):
000 123456
000 / 123456 = 0.000000
请选择要进行的大数运算:
a) 加法 b) 减法
c) 乘法 d) 除法
q) 退出
请输入你的选择: d
请输入要计算的两个数字(仅支持整数除法计算,计算结果显示6位小数):
123 123
123 / 123 = 1.000000
请选择要进行的大数运算:
a) 加法 b) 减法
c) 乘法 d) 除法
q) 退出
请输入你的选择: d
请输入要计算的两个数字(仅支持整数除法计算,计算结果显示6位小数):
111111 222222
111111 / 222222 = 0.500000
请选择要进行的大数运算:
a) 加法 b) 减法
c) 乘法 d) 除法
q) 退出
请输入你的选择: q
Comments | 2 条评论
博客作者 1989394115
1.除法的计算,可以stod,用一个double进行除法(反正都会有精度损失),
2.可以对字符串进行格式识别,比如小数形式、指数形式,(可以考虑用stoi或stod试试)
博客作者 songjiahao
@1989394115 1.主要是考虑计算的长度,可能会远远超过double表示的范围。
2.这篇博客主要目的是使用字符串实现大数运算,所以没有考虑输入的处理和解析。