高精度算法
高精度算法
本文内容较多,信息量偏大,请仔细阅读。
1.高精度算法简介
高精度算法(High Accuracy Algorithm)是处理大数字的数学计算方法。在一般的科学计算中,会经常算到小数点后几百位或者更多,当然也可能是几千亿几百亿的大数字。一般这类数字我们统称为高精度数,高精度算法是用计算机对于超大数据的一种模拟加,减,乘,除,乘方,阶乘,开方等运算。对于非常庞大的数字无法在计算机中正常存储,于是,将这个数字拆开,拆成一位一位的,或者是四位四位的存储到一个数组中, 用一个数组去表示一个数字,这样这个数字就被称为是高精度数。高精度算法就是能处理高精度数各种运算的算法,但又因其特殊性,故从普通数的算法中分离,自成一家。
2.高精数读入
方法一:使用字符数组
\(gets\) 遇回车符则认为当前字符串结束(可以读入空格);
\(scanf\)、\(cin\) 遇空格或回车符则认为当前字符串结束。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
char s[200];
int main(){
gets(s);//注,gets()在最新的c++里已经不能用了
cout<<strlen(s)<<endl<<s;
return 0;
}
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
char s[200];
int main(){
cin>>s;
cout<<strlen(s)<<endl<<s;
return 0;
}
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
char s[200];
int main(){
scanf("%s",s);
cout<<strlen(s)<<endl<<s;
return 0;
}
方法二:使用字符串
\(cin\) 遇空格或回车符则认为当前字符串结束;
\(getline\) 遇回车符则认为当前字符串结束(可以读入空格)。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
string s;
int main(){
getline(cin,s);
cout<<s.length()<<endl<<s;
return 0;
}
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
string s;
int main(){
cin>>s;
cout<<s.length()<<endl<<s;
return 0;
}
3.高精数存储
s1="123456789"
a[0] | a[1] | a[2] | a[3] | a[4] | a[5] | a[6] | a[7] | a[8] |
---|---|---|---|---|---|---|---|---|
9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
s2="3456"
a[0] | a[1] | a[2] | a[3] |
---|---|---|---|
6 | 5 | 4 | 3 |
一般是倒着存(a[0]是个位,最后的元素是最高位)。
1、需要先计算个位,逆序存放方便对齐。
2、方便进位。
代码:
la=s.size();//用la存放字符串s的位数
for(i=0;i<la;i++)//将数串s转换为数组a,注意:倒序存储
a[i]=s[la-1-i] -'0';
4.高精加
进位
a[6] | a[5] | a[4] | a[3] | a[2] | a[1] | a[0] |
---|---|---|---|---|---|---|
+ | b[4] | b[3] | b[2] | b[1] | b[0] | |
c[6] | c[5] | c[4] | c[3] | c[2] | c[1] | c[0] |
加法法则:个位对齐,逢十进一。
代码实现
方法一:设置一个进位变量 \(m\)
if(la>lb) lc=la;//lc取最长位
else lc=lb;
m=0;
for(int i=0;i<lc;i++){
c[i]=(m+a[i]+b[i])%10;
m=(m+a[i]+b[i])/10;
}
if(m>0){//最高位有进位时
c[lc]=m;
lc++;
}
方法二:先计算,最后处理进位
for(int i=0;i<lc;i++)
c[i]=a[i]+b[i];
for(int i=0;i<lc;i++){
c[i+1]=c[i+1]+c[i]/10;
c[i]=c[i]%10;
}
if(c[lc]>0) lc++;
a[3] | a[2] | a[1] | a[0] | |
---|---|---|---|---|
6 | 2 | 4 | 7 | |
+ | 3 | 7 | 9 | 5 |
9 | 9 | 13 | 12 | |
1 | 0 | 0 | 4 | 2 |
c[4] | c[3] | c[2] | c[1] | c[0] |
改进:去掉 \(c\) 数组
for(int i=0;i<la;i++)
a[i]=a[i]+b[i];
for(int i=0;i<a;i++){
a[i+1]+=a[i]/10;
a[i]%=10;
}
if(a[la]>0) la++;
代码
#include<iostream>
#include<string>
using namespace std;
int main(){
int a[205]={0},b[205]={0};
string a1,b1;
cin>>a1>>b1;
int la=a1.length(),lb=b1.length();
for(int i=0;i<la;i++)
a[i]=a1[la-1-i]-'0';
for(int i=0;i<lb;i++)
b[i]=b1[lb-1-i]-'0';
if(lb>la) la=lb;
for(int i=0;i<la;i++)
a[i]+=b[i];
for(int i=0;i<la;i++){
a[i+1]+=a[i]/10;
a[i]%=10;
}
if(a[la]>0) la++;
while(la>0&&a[la-1]==0) la--;//删除前导零
for(int i=la-1;i>=0;i--)
cout<<a[i];
return 0;
}
5.高精减(跟高精加区别不大,所以不细说了)
借位
for(int i=0;i<la;i++){
if(a[i]<b[i]){
a[i+1]--;//跟一般的竖式思想一样,前一位减1
a[i]+=10;//本位加十
}
a[i]-=b[i];
}
代码(不是最终版)
#include<iostream>
#include<string>
using namespace std;
int main(){
int a[205]={0},b[205]={0};
string a1,b1;
cin>>a1>>b1;
int la=a1.length(),lb=b1.length();
if(a1==b1){
cout<<0;
return 0;
}
for(int i=0;i<la;i++)
a[i]=a1[la-1-i]-'0';
for(int i=0;i<lb;i++)
b[i]=b1[lb-1-i]-'0';
for(int i=0;i<la;i++){
if(a[i]<b[i]){
a[i]+=10;
a[i+1]-=1;
}
a[i]-=b[i];
}
while(la>0&&a[la-1]==0) la--;
for(int i=la-1;i>=0;i--)
cout<<a[i];
return 0;
}
但我们还可能遇到结果是负数的情况
所以,我们要在代码前面判断哪个数大。
如果第二个数大,那么我们就先输出负号,再让第二个数去减第一个数。
判断代码如下:
if(la<lb||la==lb&&a1<b1){
swap(a1,b1);
swap(la,lb);
cout<<"-";
}
在原始代码上加上判断代码即可
6.高精乘
高精乘单精
9 | 9 | 9 | ||
---|---|---|---|---|
* | 9 | 9 | ||
891 | 891 | 891 | ||
9 | 8 | 9 | 0 | 1 |
分析:
1.\(a\) 的每一位都单独与 \(b\) 相乘;
2.再由低到高位依次处理进位;
3.最后处理最高位。
代码(高精乘单精)
#include<iostream>
#include<string>
using namespace std;
int main(){
int la,b,m,a[260]={0};
string a1;
cin>>a1>>b;
la=a1.size();
for(int i=0;i<la;i++)
a[i]=a1[la-i-1]-'0';
for(int i=0;i<la;i++)
a[i]=a[i]*b;
for(int i=0;i<la;i++){
a[i+1]=a[i+1]+a[i]/10;
a[i]=a[i]%10;
}
m=a[la]; //最高位
while(m>0){ //可能有多次进位
a[la]=m%10;
m/=10;
++la;
}
for(int i=la-1;i>=0;i--)
cout<<a[i];
return 0;
}
高精乘高精
分析:
假设乘数和被乘数的长度分别为 \(la\) 和 \(lb\),
则最后的乘积长度最长为 \(la+lb\) ,最小是 \(la+lb-1\)。
下标关系:
a[2] | a[1] | a[0] | ||
---|---|---|---|---|
b[1] | b[0] | |||
a[2]*b[0] | a[1]*b[0] | a[0]*b[0] | ||
a[2]*b[1] | a[1]*b[1] | a[0]*b[1] | ||
c[4] | c[3] | c[2] | c[1] | c[0] |
代码(高精乘高精)
#include<iostream>
#include<string>
using namespace std;
int main(){
int a[205]={0},b[205]={0},c[205]={0};
string a1,b1;
cin>>a1>>b1;
int la=a1.size(),lb=b1.size(),lc=la+lb;
for(int i=0;i<la;i++)
a[i]=a1[la-i-1]-'0';
for(int i=0;i<lb;i++)
b[i]=b1[lb-i-1]-'0';
for(int i=0;i<la;i++)
for(int j=0;j<lb;j++)
c[i+j]+=a[i]*b[j];
for(int i=0;i<lc;i++){
c[i+1]+=c[i]/10;
c[i]%=10;
}
while(lc>0&&c[lc-1]==0) lc--;
for(int i=lc-1;i>=0;i--)
cout<<c[i];
return 0;
}
7.高精除
高精除以单精(与手工除类似)
#include<iostream>
#include<string>
using namespace std;
int main(){
int a[205]={0},b,c[205]={0};
string a1;
cin>>a1>>b;
int la=a1.size(),lc=0,ys=0;
for(int i=0;i<la;i++)
a[i]=a1[i]-'0';
for(int i=0;i<la;i++){
c[i]=(ys*10+a[i])/b;
ys=(ys*10+a[i])%b;
}
while(c[lc]==0&&lc<la-1) lc++;
for(int i=lc;i<la;i++)
cout<<c[i];
return 0;
}
高精除以高精
大体思路:
高精度除法,这个和加减乘一样,我们都要从手算的角度入手。举 一个例子,比如 \(524134\) 除以 \(123\),结果是 \(4261\)。
第一位 \(4\) 的来源是把 \(524\) 和 \(123\) 对齐,然后进行循环减法,循环了 \(4\) 次,余\(32\)。
将 \(32134\) 的前三位 \(321\)继续和 \(123\) 对齐,循环减法 \(2\) 次,余 \(75\)。
把 \(7534\) 的前三位\(753\) 和 \(123\) 对齐,循环减法 \(6\) 次,余\(15\)。
将 \(154\) 和 \(123\) 对齐,只能减 \(1\) 次,所以结果是 \(4261\)。
终止程序。