关于高精度
作者:焦祺 08-11-8
所谓的高精度运算,是指参与运算的数(加数,减数,因子……)范围大大超出了标准数据类型(整型,实型)能表示的范围的运算。
高精度的特征使我们在运用上一般会借助函数模板,不过在有的情况是需要我们更改的,所以高精度的计算还是值得我们深入研究。
高精度计算的一般分类:
l 高精度加法
l 高精度减法
l 高精度乘法(阶乘)
l 高精度除法(求余)
l 高精度数比较
高精度加法计算原理:
观察人工计算的方法:竖式加法
223
+ 996
-----------
9 3+6=9
1 2+9=11,当前位1,进位1
2 2+9+1=12,当前位2,进位1
1 进位的1
-----------
1219
算法框架:
A为AN位数,B为BN位数,结果存到C
N=AN>=BN?AN:BN;//求得两数的高位
for(从个位开始 TO 两数最高位N)
{
C的第i位 = A的第i位 + B的第i位
进位位 = C第i位 / 10
当前位的值 = C第i位 %10
}
自编高精度加法函数模板:
/**
* @函数名 : add
* @brief : 高精度加法计算
* @return : void
* @param : char ha[]
* @param : char hb[]
* @param : char answer[]
* @remark : 结果返回到answer[]
*/
#define M 500
void add(char ha[],char hb[],char answer[])
{
int a[M],b[M],c[M];
int la,lb,len,i;
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(c,0,sizeof(c));
la=strlen(ha);lb=strlen(hb); //求串长
len = la>=lb?la:lb; //求两个串长的最长者
for(i=0; i<la; i++) //将字符串a1逆序存到整型数组a
a[i] = ha[la-i-1]-'0';
for(i=0; i<lb; i++) //将字符串b1逆序存到整型数组b
b[i] = hb[lb-i-1]-'0';
for(i=0; i<len ;i++) //数字处理
{
c[i] += a[i]+b[i]; //处理a+b
c[i+1] += c[i]/10; //处理进位数
c[i] = c[i]%10; //处理原位数
}
// outputadd(c,len); //直接输出结果
if (c[len] == 0)
len--;
for (i=0; i<=len; i++)
answer[i] = c[len-i]+'0'; //把结果存到answer[]里
answer[i] = ‘/ 0’ ;
}
加法输出函数:
/**
* @函数名 : output
* @brief : 高精度加法专用输出
* @return : void
* @param : int res[] 输出的结果
* @param : int len 结果的长度
* @remark : 注意第一位是否为0,len包含前导零可能
*/
void outputadd(int res[],int len)
{
int i;
for(i=len;i>=0;i--)
{
if (res[i] == 0 && i==len)
{
continue;
}
printf("%d",res[i]);
}
printf("/n");
}
高精度加法计算的时间效率提高方法:
在运用中,加法的高精计算算是比较简单,用起来也比较那灵活多变一点,在简单的题目中可以用二维数组计算打表。这样不用每次都要去调用高精度加法函数,从而可以提高实战中的时间效率。
如:HDOJ1715 ( 大菲波数 )
#include<iostream>
#include<string.h>
using namespace std;
int a[1005][220];
int main()
{
int j,i,k,l,t,n,m,flag;
memset(a,0,sizeof(a));
a[1][0] = 1;
a[2][0] = 1;
for(i=3;i<=1000;i++)
for(j=0;j<220;j++)
{
a[i][j] += a[i-1][j]+a[i-2][j]; //
if(a[i][j] > 9)
{
a[i][j] = a[i][j] % 10; //进位
a[i][j+1]+=1;
}
}
//-----------------------------------------------------------------
scanf("%d",&n) != EOF;
while(n--)
{
scanf("%d",&m); //m为要求位
//----------------------------output:
for(j=220-1 ; j>=0 ;j--) //找出最高位
if(a[m][j] != 0)
{
flag = j;
break;
}
for(j=flag ;j>=0;j--)
printf("%d",a[m][j]);
printf("/n");
}
return 0;
}
以上的方法可以较好的解决时间效率的问题,但在有些运用中,空间效率却成为考点,由于计算高精度常用INT数组,所以优化时就要在数组上下功夫了。
如:HDOJ1250 ( Hat's Fibonacci )
#include<iostream>
using namespace std;
int num[7038][260]={0};
int main()
{
int i,j,n;
num[1][0]=1;
num[2][0]=1;
num[3][0]=1;
num[4][0]=1;
for(i=5;i<7039;i++)
{
for(j=0;j<260;j++)
num[i][j]=num[i-1][j]+num[i-2][j]+num[i-3][j]+num[i-4][j];//公式
for(j=0;j<259;j++)
if(num[i][j]>100000000)
{
num[i][j+1]+=num[i][j]/100000000;
num[i][j]%=100000000;
}
}
while(scanf("%d",&n)!=EOF)
{
for(i=259;i>0;i--)
if(num[n][i]!=0)
break;
printf("%d",num[n][i]);
for(j=i-1;j>=0;j--)
printf("%08d",num[n][j]); //上面有8个0,注意这里的输出控制
printf("/n");
}
return 0;
}
高精度减法计算原理:
竖式减法:
927
- 896
-----------
1 7-6=1
3 12-9=3,借位12
0 (9-1)-8=0
-----------
31
注意:
- 注意减法的特性:借位
- 不要忽略连续借位!
- 借位前被借位是0的情况
- 注意可能出现负数!
- 前导零处理
算法框架:
for(从个位开始 TO 两数最高位N)
{
if(a的第i位小于b的第i位)
{//那么需要借位
借位位减1
当前位加10
}
a第i位和b第i位的差存入a;
}
自编高精度减法函数模板:
/**
* @函数名 : subtract
* @brief : 高精度减法
* @return : void
* @param : char ha[] 被减数
* @param : char hb[] 减数
* @param : char answer[] 差
* @remark : 结果返回到answer[]里
*/
#define M 500
void subtract(char ha[],char hb[],char answer[])
{
int a[M],b[M];
int la,lb,len,i;
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
la=strlen(ha);lb=strlen(hb); //求串长
len = la>=lb?la:lb; //求两个串长的最长者
for(i=0; i<la; i++) //将字符串ha逆序存到整型数组a
a[i] = ha[la-i-1]-'0';
for(i=0; i<lb; i++) //将字符串hb逆序存到整型数组b
b[i] = hb[lb-i-1]-'0';
if(compare(a,la,b,lb)) //如果是正数
{
for(i=0;i<la;i++) //处理高精度减法
{
if(a[i] < b[i])
{
a[i+1]--; //借位
a[i]+=10; //借位后处理当前位加10
}
a[i] = a[i] - b[i]; //处理后存至a中
}
while(!a[la-1]) //去掉前导零
{
la--;
if(!la) //如果全是零,则只输出一个零
{
answer[0] = '0';
answer[1] = '/0';
return ;
}
}
for(i=0; i<la; i++) //从非零位开始输出
answer[i] = a[la-i-1] + '0';
}
else //如果是负数
{
for(i=0;i<lb;i++) //处理高精度减法
{
if(b[i] < a[i])
{
b[i+1]--; //借位
b[i]+=10; //借位后处理当前位加10
}
b[i] = b[i] - a[i]; //处理后存至b中
}
while(!b[lb-1])
{
lb--;
if(!lb)
{
answer[0] = '0';
answer[1] = '/0';
return ;
}
}
answer[0] = '-';
for(i=1; i<lb+1; i++) //从非零位开始输出
answer[i] = b[lb-i] + '0';
}
}
比较函数:
/**
* @函数名 : compare
* @brief : 判断哪个数大
* @return : bool
* @remark : 正数返回1,负数返回0
*/
bool compare(int a[],int la,int b[],int lb)
{
int i;
if(la>lb) //被减数位数 大于 减数位数,差一定是正数
{
return 1;
}
else if(la<lb) //被减数位数 小于 减数位数,差一定是负数
{
return 0;
}
else //la == lb 结果可能是正也可能是负
{
for (i=0;i<la;i++) //从最高位开始判断,看哪个最高位先大
{
if(a[i] > b[i])
{
return 1;
}
else if (a[i] < b[i])
{
return 0;
}
}
if(i == la)
{
return 1;
}
}
}
高精度乘法计算原理:
竖式乘法:
1. 大数乘小数,比如12345*9=?
1 2 3 4 5
* 9
--------------
45 5*9=45
36 4*9=36
27 3*9=27
18 2*9=18
+ 9 1*9=9
--------------
1 1 1 1 0 5
2. 大数乘大数,比如12345*12345=?
12345*12345=12345*5+
12345*4*10+
12345*3*100+
12345*2*1000+
12345*1*10000
问题转化为若干个大数乘小数之和
算法框架
用一个i,j二重循环{{res[i+j]+=b[i]*a[j];}}
//不管三七二十一先把结果算出来
//然后我们再来考虑进位:
for(……)
{
如果:res[i] >= 10
那么:
res[i+1] += res[i]/10; //进位位
res[i] %= 10; //原始位
}
自编高精度乘法函数模板:
/**
* @函数名 : multiply
* @brief : 高精度乘法计算
* @return : void
* @param : char ha[]
* @param : char hb[]
* @param : char answer[]
* @remark : 结果返回到answer[]
*/
#define M 500
void multiply(char ha[],char hb[],char answer[])
{
int i,j;
int la; //被乘数位数
int lb; //乘数位数
int a[M+10]; //存被乘数
int b[M+10]; //存乘数
int res[2*M+10]; //存结果
memset(answer,NULL,sizeof(answer));
memset(res,0,sizeof(res));
la = strlen(ha);
lb = strlen(hb);
for(i=0; i<la; i++) //将字符串a1逆序存到整型数组a
a[i] = ha[la-i-1]-'0';
for(i=0; i<lb; i++) //将字符串b1逆序存到整型数组b
b[i] = hb[lb-i-1]-'0';
for (i=0;i<lb;i++) //每次用b的一位,去和a的各位相乘
{
for (j=0;j<la;j++)
{
res[i+j]+=b[i]*a[j];
}
}
for (i=0;i<M*2;i++) //处理进位
{
if (res[i] >= 10)
{
res[i+1] += res[i]/10; //进位位
res[i] %= 10; //原始位
}
}
//output();
bool flag = 0;j=0;
for(i=M*2; i>=0 ;i--)
{
if(flag)
{
answer[j] = res[i] + '0';
j++;
}
else if (res[i]) //不存入前导零
{
answer[j] = res[i] + '0';
j++;
flag = 1;
}
}
if (!flag)
{
answer[0] = '0';
j++;
}
answer[j] = '/0';
}
乘法输出函数:
/**
* @函数名 : output
* @brief : 输出积
* @return : void
* @remark : 注意前导零
*/
void output(int res[])
{
bool flag = 0;
int i;
for(i=M*2; i>=0 ;i--)
{
if(flag)
{
printf("%d",res[i]);
}
else if (res[i]) //不输出前导零
{
printf("%d",res[i]);
flag = 1;
}
}
if (!flag)
{
printf("0");
}
printf("/n");
}
高精度除法计算原理:
竖式除法----用多次的减法来实现!
36
12345 √ 452678
37035 12345*3=37035
8232 45267-37035=8232
82328 下一位再次减法
74070 12345*6=74070
8258 82328-74070=8258 余数
高精度除以低精度算法框架
d = 0
for(从除数的最高位 to 除数的个位)
{
d += a的第i位
计算d和除数的商 存入C数组中
计算d和除数的余数 存入d中
处理该位的余数便是下一位的十位
}
自编高精度除以低精度函数模板:
/**
* @函数名 : divide
* @brief : 高精度除以低精度函数模板
* @return : int
* @param : char ha[]
* @param : int b
* @param : char answer[]
* @remark : 结果返回到answer[]
*/
int divide(char ha[],int b,char answer[])
{
int i,la,j;
int d; //余数
int c[N]={0}; //商
int a[N]={0}; //被除数
if (b == 0)
{
return -1;
}
la=strlen(ha);
memset(a,0,sizeof(a));
memset(answer,NULL,sizeof(answer));
for(i=0;i<la;i++)
a[i] = ha[la-i-1]-'0';
d=0;
for(i=la-1;i>=0;i--)
{
d = d*10 + a[i]; //取a的最高位开始
c[i] = d/b; //把商一位一位存入C中
d=d%b; //该位的余数便是下一位的十位数
}
while(c[la-1]==0 && la>1)
la--;
// 输出结果
// for(i=la-1; i>=0; i--)
// printf("%d",c[i]);
j=0;
for (i=la-1;i>=0;i--)
{
answer[j] = c[i]+'0';
j++;
}
answer[j] = '/0';
return d;
}
高精度除以高精度算法框架
d=0;//余数
for(从除数的最高位 to 除数的个位)
{
d自乘10 //高精度乘10
被除数的第i位加入d中
while(d大于除数)
{
d和除数的差存在d中
商++;
}
}
高精度除法函数模板:
#define N 1001
int compare(char * c1,int s1,int e1,char *c2,int s2,int e2)/*大整数比较大小*/
{
while(c1[s1]=='0'&&s1<e1)s1++;
while(c2[s2]=='0'&&s2<e2)s2++;
if(e1-s1>e2-s2)return 1;
if(e1-s1<e2-s2)return -1;
int i1;int f11=0;
for(i1=0;i1<=e1-s1;i1++)
{
if(c1[s1+i1]>c2[s2+i1]){f11=1;break;}
if(c2[s2+i1]>c1[s1+i1])break;
}
if(i1>e1-s1)return 0;
if(f11)return 1;
return -1;
}
void sub(char *c1,int l1,char *c2,int n,int l)/*两个大整数相减,结果放在c1中*/
{
int fig=0;
if(l1>l)
{
fig=1;
int i5;
for(i5=l;i5>0;i5--)
c2[i5]=c2[i5-1];
c2[0]='0';l++;
}
int jw=0;
int i4;
char tc[N];
for(i4=l-1;i4>=0;i4--)
{
tc[i4]=((c2[i4]-48)*n+jw)%10+48;
jw=((c2[i4]-48)*n+jw)/10;
}
jw=0;
for(i4=l-1;i4>=0;i4--)
{
int ttt=jw;
if(c1[i4]-tc[i4]-jw>=0)
{
tc[i4]=c1[i4]-tc[i4]-jw+48;
jw=0;
}
else
{
jw=(tc[i4]-c1[i4]+jw);
if(jw%10==0)jw/=10;
else jw=jw/10+1;
tc[i4]=jw*10+c1[i4]-tc[i4]-ttt+48;
}
}
if(fig)
{
for(i4=0;i4<l-1;i4++)c2[i4]=c2[i4+1];
l--;
c2[l]='/0';
}
tc[l1]='/0';
for(i4=0;i4<=l1;i4++)
c1[i4]=tc[i4];
}
void high_precise_division(char *c1,char *c2)
{
int len1,len2;
len1=strlen(c1);
len2=strlen(c2);
int i,j,k,ip;
i=0;
while(c1[i]=='0'&&i<len1-1)i++;
for(j=0;i<len1;j++,i++)c1[j]=c1[i];
len1=j;c1[j]='/0';
i=0;
while(c2[i]=='0'&&i<len2-1)i++;
for(j=0;i<len2;j++,i++)c2[j]=c2[i];
len2=j;c2[j]='/0';
/*while(c1[0]=='0'&&i<len1-1)
{
for(k=0;k<len1-1;k++)
c1[k]=c1[k+1];
len1--;
}去掉前面的0*/
/*while(c2[0]=='0'&&j<len2-1)
{
for(k=0;k<len2-1;k++)
c2[k]=c2[k+1];
len2--;
}去掉前面的0*/
c1[len1]='/0';
c2[len2]='/0';
if(strcmp(c2,"0")==0)return ;/*当除数为0时*/
if(strcmp(c1,"0")==0){strcpy(c2,"0");return ;}/*当被除数为0时*/
if(len1<len2||len1==len2&&compare(c1,0,len1-1,c2,0,len2-1)<0)
{
strcpy(c2,c1);
c1[0]='0';c1[1]='/0';
return ;
}
else
{
ip=0;
char product[N],*pr;/*部分积*/
pr=product;
for(ip=0;ip<len2-1;ip++)
pr[ip]=c1[ip];
for(i=0;i<=len1-len2;i++)
{
pr[ip++]=c1[len2-1+i];
if(ip>=len2 && compare(pr,0,ip-1,c2,0,len2-1)>=0)
{
char tc[N];
for(j=1;j<=9;j++)
{
for(k=0;k<ip;k++)tc[k]=pr[k];
sub(tc,ip,c2,j,len2);/*pr-c2*j结果放在pr中*/
for(k=0;tc[k]!='/0';k++);
if(compare(tc,0,k-1,c2,0,len2-1)<0)
break;
}
strcpy(pr,tc);
ip=strlen(pr);
c1[i]=j+48;
while(pr[0]=='0'&&ip>1)
{
for(j=0;j<ip-1;j++)
pr[j]=pr[j+1];
ip--;
}
if(ip==1&&pr[0]=='0')ip--;
}
else c1[i]='0';
}
while(c1[0]=='0')
{
for(j=0;j<i-1;j++)
c1[j]=c1[j+1];
i--;
}
c1[i]='/0';
if(ip==0){pr[0]='0';pr[1]='/0';ip=1;}
else
{
while(pr[0]=='0'&&ip>1)
{
for(j=0;j<ip-1;j++)pr[j]=pr[j+1];
ip--;
}
}
for(j=0;j<ip;j++)
c2[j]=pr[j];
c2[ip]='/0';
return ;
}
}
int main()
{
int i,j;
char c1[N],c2[N];/*整数高精度除法*/
while(scanf("%s %s",c1,c2)!=EOF)
{
char *pc1,*pc2;
pc1=c1;pc2=c2;
c1[strlen(c1)]='/0';
c2[strlen(c2)]='/0';
high_precise_division(pc1,pc2);/*结果放在pC1中,余数放在pC2中*/
printf(" 商:%s/n余数:%s/n",pc1,pc2);
}
return 0;
}
高精度阶乘函数模板:
语法:int result=factorial(int n);
参数:
n:n 的阶乘
返回值:阶乘结果的位数
注意:
本程序直接输出n!的结果,需要返回结果请保留long a[]
需要 math.h
源程序:
int factorial(int n)
{
long a[10000];
int i,j,l,c,m=0,w;
a[0]=1;
for(i=1;i<=n;i++)
{
c=0;
for(j=0;j<=m;j++)
{
a[j]=a[j]*i+c;
c=a[j]/10000;
a[j]=a[j]%10000;
}
if(c>0) {m++;a[m]=c;}
}
w=m*4+log10((double)a[m])+1;
printf("%ld",a[m]);
for(i=m-1;i>=0;i--) printf("%4.4ld",a[i]);
return w;
}
PKU上关于高精度的题目汇集:
1001(高精度乘法) 2413(高精度加法,还有二分查找)
1220, 1405, 1503, 1131, 2305,2325,2389,1604,1047
1504 1517 1519 1547 1552 1563(考虑仔细一点,还要注意精度)
2381 2385 2393 2394 2395 2413(高精度基础) //2418 2419
参考文献:
《程序设计基础第2版》吴文虎 清华大学出版社
《ACM程序设计培训教程》 中国铁道出版社