一、引入
1.简介
高精度计算(Arbitrary-Precision Arithmetic),也被称作大整数(bignum)计算,运用了一些算法结构来支持更大整数间的运算(数字大小超过语言内建整型)。
2.表示
在平常的实现中,高精度数字利用字符串表示,每一个字符表示数字的一个十进制位。因此可以说,高精度数值计算实际上是一种特别的字符串处理。
读入字符串时,数字最高位在字符串首(下标小的位置)。但是根据我们日常生活中计算的习惯,下标最小的位置存放的是数字的最低位,即存储反转的字符串。这么做还有一个好处就是,数字的长度可能发生变化,但同样权值位始终保持对齐。
ll la=a.size(),lb=b.size();
for(ll i=0;i<la;i++)
na[la-1-i]=a[i]-'0';
for(ll i=0;i<lb;i++)
nb[lb-1-i]=b[i]-'0';
3.时间复杂度说明
对于乘法和除法运算,由于时间复杂度上的需求,我们将会分成单精度与高精度运算,高精度与高精度运算。
- 单精度与高精度的运算时间复杂度为: O ( n ) O(n) O(n)
- 高精度与高精度的运算时间复杂度为: O ( n 2 ) O(n^2) O(n2)
二、加减法
1.加法
高精度加法的原理其实就是竖式加法。也就是从最低位开始,将两个加数对应位置上的数码相加,并判断是否达到或超过 10 10 10。如果达到,那么处理进位:将更高一位的结果上增加 1 1 1,当前位的结果减少 10 10 10。
限制 a , b a,b a,b两个字符串都代表非负整数,且无前导零。
string add(string a,string b)
{
string ans;
memset(na,0,sizeof(na));
memset(nb,0,sizeof(nb));
ll la=a.size(),lb=b.size();
for(ll i=0;i<la;i++)
na[la-1-i]=a[i]-'0';
for(ll i=0;i<lb;i++)
nb[lb-1-i]=b[i]-'0';
ll lmax=la>lb?la:lb;
for(ll i=0;i<lmax;i++)
{
na[i]+=nb[i];
na[i+1]+=na[i]/10;
na[i]%=10;
}
if(na[lmax])
lmax++;
for(ll i=lmax-1;i>=0;i--)
ans+=na[i]+'0';
return ans;
}
2.减法
高精度减法的原理其实就是竖式减法。也就是从个位起逐位相减,遇到负的情况则向上一位借 1 1 1。整体思路与加法完全一致。由于减法可能会导致多位为 0 0 0,所以需要人为去除前导 0 0 0。
限制 a , b a,b a,b两个字符串都代表非负整数,且无前导零。
string sub(string a,string b)
{
memset(na,0,sizeof(na));
memset(nb,0,sizeof(nb));
bool flag=false;
if(a.size()<b.size() || (a.size()==b.size() && a<b))
{
swap(a,b);
flag=true;
}
string ans;
ll la=a.size(),lb=b.size();
for(ll i=0;i<la;i++)
na[la-1-i]=a[i]-'0';
for(ll i=0;i<lb;i++)
nb[lb-1-i]=b[i]-'0';
int lmax=la>lb?la:lb;
for(int i=0;i<lmax;i++)
{
na[i]-=nb[i];
if(na[i]<0)
{
na[i]+=10;
na[i+1]--;
}
}
while(!na[--lmax] && lmax>0)
;
lmax++;
for(ll i=lmax-1;i>=0;i--)
ans+=na[i]+'0';
if(flag)
cout<<"-";
return ans;
}
三、乘法
1.单精度乘高精度
先考虑一个简单的情况:乘数中的一个是普通的整型数。即 a , b a,b a,b中 b b b为
一个直观的思路是直接将 a a a每一位上的数字乘以 b b b。从数值上来说,这个方法是正确的,但它并不符合十进制表示法,因此需要将它重新整理成正常的样子。
重整的方式,也是从个位开始逐位向上处理进位。但是这里的进位可能非常大,甚至远大于 9 9 9,因为每一位被乘上之后都可能达到 9 b 9b 9b 的数量级。所以这里的进位不能再简单地进行 − 10 -10 −10运算,而是要通过除以 10 10 10的商以及余数计算。
单精度乘高精度指一个整型变量乘以一个超过c++整数表示范围的数。
string mul(string a,ll b)
{
string ans;
ll La=a.size();
memset(na,0,sizeof(na));
for(ll i=La-1;i>=0;i--)
na[La-i-1]=a[i]-'0';
ll w=0;
for(ll i=0;i<La;i++)
na[i]=na[i]*b+w,w=na[i]/10,na[i]=na[i]%10;
while(w)
na[La++]=w%10,w/=10;
La--;
while(La>=0)
ans+=na[La--]+'0';
return ans;
}
2.高精度乘高精度
如果两个数都是高精度,直接仿照竖式乘法的模式即可。
高精度乘高精度指两个超过c++整数表示范围的数。
string mul(string a,string b)
{
memset(na,0,sizeof(na));
memset(nb,0,sizeof(nb));
memset(nc,0,sizeof(nc));
string s;
ll La=a.size(),Lb=b.size();
for(ll i=La-1;i>=0;i--)
na[La-i]=a[i]-'0';
for(ll i=Lb-1;i>=0;i--)
nb[Lb-i]=b[i]-'0';
for(ll i=1;i<=La;i++)
for(ll j=1;j<=Lb;j++)
nc[i+j-1]+=na[i]*nb[j];
for(ll i=1;i<=La+Lb;i++)
nc[i+1]+=nc[i]/10,nc[i]%=10;
if(nc[La+Lb])
s+=nc[La+Lb]+'0';
for(ll i=La+Lb-1;i>=1;i--)
s+=nc[i]+'0';
return s;
}
四、除法
除法的原理与乘法相同,这里不再赘述。
1.多精度除以单精度
string div(string a,ll b)//高精度a除以单精度b
{
string r,ans;
ll d=0;
if(a=="0")
return a;
for(ll i=0;i<a.size();i++)
{
r+=(d*10+a[i]-'0')/b+'0';//求出商
d=(d*10+(a[i]-'0'))%b;//求出余数
}
ll p=0;
for(ll i=0;i<r.size();i++)
{
if(r[i]!='0')
{
p=i;
break;
}
}
return r.substr(p);
}
2.多精度除以多精度
ll sub(ll *a,ll *b,ll La,ll Lb)
{
if(La<Lb)
return -1;
if(La==Lb)
{
for(ll i=La-1;i>=0;i--)
{
if(a[i]>b[i])
break;
else if(a[i]<b[i])
return -1;
}
}
for(ll i=0;i<La;i++)
{
a[i]-=b[i];
if(a[i]<0)
{
a[i]+=10;
a[i+1]--;
}
}
for(ll i=La-1;i>=0;i--)
if(a[i])
return i+1;
return 0;
}
void div(string n1,string n2)
{
string s,v;
ll a[maxn],b[maxn],r[maxn],La=n1.size(),Lb=n2.size(),i,tp=La;
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(r,0,sizeof(r));
for(i=La-1;i>=0;i--)
a[La-1-i]=n1[i]-'0';
for(i=Lb-1;i>=0;i--)
b[Lb-1-i]=n2[i]-'0';
if(La<Lb || (La==Lb && n1<n2))
{
cout<<"0 "<<n1<<endl;
return;
}
ll t=La-Lb;
for(ll i=La-1;i>=0;i--)
{
if(i>=t)
b[i]=b[i-t];
else
b[i]=0;
}
Lb=La;
for(ll j=0;j<=t;j++)
{
int temp;
while((temp=sub(a,b+j,La,Lb-j))>=0)
{
La=temp;
r[t-j]++;
}
}
for(i=0;i<maxn-10;i++)
{
r[i+1]+=r[i]/10;
r[i]%=10;
}
while(!r[i])
i--;
while(i>=0)
s+=r[i--]+'0';
i=tp;
while(!a[i])
i--;
while(i>=0)
v+=a[i--]+'0';
if(v.empty())
v="0";
if(v!="0")
cout<<s<<" "<<v<<endl;
else
cout<<s<<endl;
}
五、未来
大家不难发现,高精度计算有时会达到 O ( n 2 ) O(n^2) O(n2)的时间复杂度,这样的复杂度在很多时候都是不可接受的,尤其是高精度仅仅是一个工具的情况下。故而还有一些更好的方式来实现高精度计算:
- 压位高精度计算
- K a r a t s u b a Karatsuba Karatsuba乘法: O ( n l o g 2 3 ) O(n^{log_23}) O(nlog23)
- 快速傅里叶变换: O ( n l o g n ) O(nlogn) O(nlogn)