大数运算

最近在做OJ题时遇到了一个求阶乘的题,看到数据范围时惊了我一跳,题目要求输入一个整数NN!,其中N的范围为0=<N<=10000。我们要知道21!已经超过64位整数的范围了,那10000!将会是多么大的数据。于是我就想到了,大数乘法!

大数运算有很多方式。

一种方式是利用字符串数组存放数据,即每一个字节存放一位10进制数,然后模拟我们手算方式进行计算。

大数加法(10进制法)

char a[100]="111111111111111111111111111";

char b[100]="2222222222222222222";

char c[101]={0};//存放a+b结果

一个n位数加上一个m位数,其结果最多为 max(n,m)+1 位。

我们手算加法时,一般从低位开始算起,这里我们也一样,从字符串末尾开始处理,将结果从C数组的末尾向开头存放,等到运算完成之后,去掉前导的0即可。

for(ic=101-1; ia>=0 && ib>=0; --ib,--ic)

{

c[ic] += a[ia]-48 + b[ib]-48;

c[ic-1] = c[ic]/10; //向高位进1

c[ic] = c[ic]%10 + 48; //如果当前位超过10,则取于是即可,然后+48转换成字符。

}

a多出来的部分放到c中。

while(ia>=0)

{

c[ic] = c[ic]-48 + a[ia]+48;

c[ic-1] = c[ic]/10; //向高位进1

c[ic] = c[ic]%10 + 48; //如果当前位超过10,则取于是即可,然后+48转换成字符。

++ic;

++ia;

}

同理,将b多出来的部分放到C中。

while(ib>=0)

{

...a

}

判断最高位是否有进位

if(c[ic] != 0)

{

c[ic]+=48;//转换成字符

}

else

{

++ic; //ic指向的位置为第一个有效字符的位置

}

如果这是一个大数加法函数的话,可以将结果字符数组作为返回值返回,则可以有个技巧可用。

即:return c+ic;

也可以将后面的数据挪到c数组开头。

以上就是大整数加法的大致过程。最需要关注的就是进位部分,即:

c[ic-1] = c[ic]/10; //向高位进1

c[ic] = c[ic]%10 + 48; //如果当前位超过10,则取于是即可,然后+48转换成字符。

大整数加法(10亿进制法)

当然还有一种使用使用整数数组来存放数据,进行运算的。已知一个int类型变量最大有符号值是21亿,10位数,我们可以将超过9位数的部分放到另一个int类型变量中去。也就是说我们的整数变成了10亿进制数了,即逢10亿进1

假设有两个大整数:

22222123456789

1111987654321

我们用整数数组来存放它们。

int a[10]={123456789,22222};//a[0]=123456789,a[1]=22222

int b[10]={987654321,1111};//b[0]=987654321,b[1]=1111

int c[11]={0};

这样存贮,运算起来将变得更放便了。

maxLenab数组中,使用到的int型变量个数。

for(i=0;i<maxLen;++i)

{

c[i] += a[i]+b[i];

c[i+1]=c[i]/10 000 000 000;

c[i] %= 10 000 000 000;

}

判断最高位是否有进位

if(c[maxLen] != 0) ++maxLen;

这样,很方便就计算完事了。如果要输出结果,我们只需要将最高位数(即 maxLen-1 下标对应的那个数)按%d格式输出(可以忽略前导的0),其余各位转换成9位的字符串输出,就OK了。

例如:

printf("%d",c[maxLen-1]); //先将高位数按整数格式输出,以忽略前导0
 for(i=maxLen-2;i>=0;--i)//将后面的数依次转换为5位字符串输出
 {
 printf("%s",itoa(c[i],9,temp));
 }
itoa是我自己写的整数到字符串的转换函数,具体实现,可以参看我后面写的求10000!的程序。

这种做法很简洁,而且计算效率会很高,难点就是如何将输入的数截断,即转换成10亿进制数。这里又将涉及到字符串处理了。

char str[]="223456789123456789";

我们需要从整数的低位,即字符串末尾,开始分离,这样才能先分离出低位10亿进制数。

int data=0;

int t=0;

int j=0;

for i:=n-1 to 0

do

data += (str[i]-48)*10^(t);

++t;

if(t == 10)

a[j++]=data;

data=0;

t=0;

大整数乘法

我们同样可以模拟手算乘法来计算大整数的乘法。

int la[],int lb[],分别存放被乘数和乘数,int lc[]存放相乘结果,与上面加法类似,我们需要从数字的低位向高位运算,对于数组来说,就需要从低下标向高下标计算(因为我们的la[0]存放的是最低位。)

至于每一个int变量应该存放多大的数字最合适,我们需要考虑到两个数相乘结果不能超过int范围,即不能超过10位十进制数。已知n位数与m位数相乘结果最多为 (n+1)+(m+1)-1 = n+m+1位。我们的乘数和被乘数将采用同一个进制,即整数的位数将相同,所以有 n+n+1<10(注意,不能取到10位,因为int最大有符号数为21亿) ,n<4.5,所以每个int值取4位十进制数最合适,即我们要将数变换成了10000进制数,逢100001

假设有两个大整数:

22222123456789

1111987654321

我们用整数数组来存放它们。

int la[10]={1234,5678,9222,22};//la[0]=1234,la[1]=5678,la[2]=9222,la[3]=22

int lb[10]={9876,5432,1111,1};//lb[0]=9876,lb[1]=5432,lb[2]=1111,lb[3]=1

int lc[21]={0};

例:模拟手算乘

for(ib=0; ib<lenb; ++ib)

{

for(ia=0; ia<lena; ++ia)

{

ic=ib+ia;

lc[ic] += la[ia]*lb[ib];

lc[ic+1] += lc[ic]/10000;//注意这里是+=

lc[ic] %= 10000;

}

}

其余的处理,与上面的大整数加法类似。

大整数减法与加法类似,而除法与减法是同一个原理,可以使用减法来实现除法。如果要得到高效的算法,可以参考计算机组成原理,了解2进制的算术运算算法,同理就可以应用到任意进制的运算中。

以上部分,可能有很多漏洞,欢迎大家指点。

10000以内阶乘的源代码gcc

#include <stdio.h>
#include <string.h>

#define MAX_DATA_LENGTH 10000 //数据最大长度

//#define TEST
#ifdef TEST
#define MAX_NJ1 20
__int64 nj1[100];
#endif

int njnow[MAX_DATA_LENGTH+1];//记录当前阶乘值
int njlast[MAX_DATA_LENGTH+1];//记录上次阶乘值

/*整数转换成字符串
**a:待转换整数
**bits:要转换的十进制位数
**destStr:目标字符串
*/
char* itoa(int a,int bits,char *destStr)
{
 int i;
 for(i=bits-1;i>=0;--i)
 {
 destStr[i]=a%10+48;
 a/=10;
 }
 return destStr;
}

/* 大整数乘法
** now:存放结果
** last:存贮被乘数数组
** len:使用到的数组位数
** n:乘数
** 返回值:使用到的数组位数
*/
int mul(int now[],int last[],int len,int n)
{
 /*
 设lalc是大整数,b32位整数
 将大整数看成100000进制数,即逢1000001,这样可以使用
 1
 la=la0*10^0+la1*10^5+la2*10^10+...
 lc=la*b=lc0*10^0+lc1*10^5+lc2*10^10...
 lc0=la0*b%10^5
 lc1=la1*b%10^5+la0*b/10^5
 lc2=la2*b%10^5+la1*b/10^5
 */
 int i;
 for(i=0;i<len;++i)
 {
 now[i] += last[i]*n;
 now[i+1] = now[i]/100000;
 now[i] %= 100000;
 }
 if(now[i] !=0 )++len;
 return len;
}

/* 获得n的阶乘值
** pLength:存放用到的数组位数
** 返回值:结果数组指针
*/
int* getNJ(int n,int *pLenth)
{
 int i;
 int *pNow=njnow;
 int *pLast=njlast;
 int *pTemp=NULL;
 int len=1;
 pNow[0]=1;
 for(i=1;i<=n;++i)
 {
 pTemp=pNow;
 pNow=pLast;
 pLast=pTemp;
 memset(pNow,0,sizeof(int)*len);
 len=mul(pNow,pLast,len,i);
 }
 *pLenth=len;
 return pNow;
}

#ifdef TEST
/*使用64位整数来计算阶乘
*/
void setNJ()
{
 int i;
 nj1[0]=1;
 for(i=1;i<=MAX_NJ1;++i)
 {
 nj1[i]=i*nj1[i-1];
 }
}
#endif

int main()
{
 int n,i;
 char temp[10];
 int *pNJ=NULL;
 int len=0;
#ifdef TEST
 setNJ();
#endif
 memset(temp,0,sizeof(temp));
 while(scanf("%d",&n) == 1)
 {
#ifdef TEST
 if(n<=MAX_NJ1)
 printf("%I64d\n",nj1[n]); //用于比较大数运算结果的正确性
#endif
 pNJ=getNJ(n,&len);
 printf("%d",pNJ[len-1]); //先将高位数按整数格式输出,以忽略前导0
 for(i=len-2;i>=0;--i)//将后面的数依次转换为5位字符串输出
 {
 printf("%s",itoa(pNJ[i],5,temp));
 }
 printf("\n");
 }
 return 0;
}

大整数阶乘2

最近又做了一道大整数阶乘题,浏览量一下高手的代码,发现一些技巧。

输出格式:不需要将整数转换成字符串输出,直接使用整数格式化输出%n.nd(例如:四位整数,%4.4d)。如果整数不足n位,则在前面补0,总共补齐n位。

技巧性的代码,果然很简洁哦!

#include <stdio.h>
#include <stdlib.h>
int a[100];
int factorial(int n)
{
 int len=1;
 int i,j;
 int c;//进位
 div_t temp;
 a[0]=1;
 for(i=1;i<=n;++i)
 {
 c=0;
 for(j=0;j<len;++j)
 {
 temp=div(a[j]*i+c,10000);//div为求商、余函数
 a[j]=temp.rem;//获得商
 c=temp.quot;//获得余数
 }
 if(c>0)//进位
 {
 a[len++]=c;
 }
 }
 return len;
}
int main()
{
 int n;
 int len,i;
 while(1)
 {
 scanf("%d",&n);
 if(n<0)break;
 len=factorial(n);
 printf("%d",a[--len]);//输出最高位
 for(i=len-1;i>=0;--i)
 {
 printf("%4.4d",a[i]);
 }
 printf("\n");
 }
 return 0;
}

不过,如果是多组测试数据,显然,上面的代码没有太大的优势。所以我们可以,牺牲空间,换时间。

#include <stdio.h>
#include <stdlib.h>
int a[101][100];
int length[101];
int fact(int n)
{
 int len=length[n-1];
 int i;
 int c=0;//进位
 div_t temp;
 for(i=0;i<len;++i)
 {
 temp=div(a[n-1][i]*n+c,10000);
 a[n][i]=temp.rem;
 c=temp.quot;
 }
 if(c>0)
 {
 a[n][len++]=c;
 }
 length[n]=len;
 return len;
}
int factorial(int n)
{
 if(length[n]==0)
 {
 factorial(n-1);//先计算n-1的阶乘
 length[n]=fact(n);//计算n的阶乘
 }
 return length[n];
}
int main()
{
 int n;
 int len,i;
 length[0]=1;
 a[0][0]=1;
 while(1)
 {
 scanf("%d",&n);
 if(n<0)break;
 len=factorial(n);
 printf("%d",a[n][--len]);
 for(i=len-1;i>=0;--i)
 {
 printf("%4.4d",a[n][i]);
 }
 printf("\n");
 }
 return 0;
}

你可能感兴趣的:(c,算法,gcc,测试,div)