最近看到一条题目,计算n!,阶乘由于它的特殊性,需要涉及到大数运算,本文在32位机器下描述一个简单的计算方法。
网上有很多种方法,本文就用数组的形式来保存计算结果,首先可以知道unsigned的数据范围是0-2^32-1,因此常规的想法就是将进制提升到2^32,从而用一个数组来保存计算结果,下面给出具体代码:
int bigNum( int x, unsigned *p, int len )
{
if ( 13 > x || p == NULL || len <= 0 )
return -1;
p[0] = 479001600;
int c = 1;
__int64 base = 0x100000000;
for ( int i=13; i<=x; i++ )
{
int addbit = 0;
__int64 num = 0;
for ( int j=0; j<c; j++ )
{
num = p[j];
num = num*i + addbit;
if ( num <= base-1 )
{
p[j] = (unsigned)num;
addbit = 0;
}
else
{
p[j] = num%base;
addbit = num/base;
}
}
if ( addbit != 0 )
p[c++] = addbit;
}
return c;
}
首先,根据计算可知,12!=479001600,没有超过unsigned的范围,上述代码计算13及以上的阶乘,计算过程就是将当前数i依次与结果数组p中的每个数依次相乘,然后加上上一轮的进位,在计算过程中用到了64位num来辅助计算进位,从而防止发生溢出,这个程序本身没有问题,但是调用以后的结果是这样的:
unsigned buf[200] = {0};
int c = bigNum( 13, buf, 200 );
for(int i=0; i<c; i++)
cout<<buf[i]<<" ";
输出:
1932053504 1
这样的结果太不直观,可想而知,当计算较大的数的时候,得出的是一堆高低反序,并且以2^32为进制的数组数据,想要直观的输出的十进制数据,貌似好像比较麻烦,我在这里也踌躇了一会儿,转换起来太麻烦,后来仔细一想我可以以2^32为进制,那么同样我也可以以10^x为进制,上述代码我们采用的是unsigned数组来保存最后结果,而unsigned最多可以包含10位10进制数,一次我们可以将进制定为10^9,因此只要将上述代码中的的base修改一下即可:
_int64 base = 1000000000;
然后运行上述测试代码,输出
227020800 6
反过来输出即可得到正确结果6227020800,此处还有需要注意的地方,就是数组中可能省略了前面的0,因此可以将输出部分修改如下:
printf("%d",buf[c-1]);
for(int i=c-2;i>=0;i--)
printf("%09d",buf[i]);
需要反序输出,另外出最后一个外,其余需要补齐中间的0
void CalcNN(int n, char *pOut)
{
if ( n <= 0 || pOut == NULL )
return;
if ( n<=12 )
{
unsigned num = 1;
for ( int i=1; i<=n; i++ )
num *= i;
sprintf( pOut, "%d", num );
return;
}
unsigned buf[300] = {0};
int c = bigNum(n, buf, 300);
sprintf( pOut, "%d", buf[c-1] );
for ( int i=c-2; i>=0; i-- )
{
int len = strlen(pOut);
sprintf( pOut+len, "%09d", buf[i] );
}
}
上述代码将最后的结果以字符串的形式保存在pOut所指向的缓存空间中。
总结:代码本身不复杂,主要的是一个思想,我们可以利用一个较大数据范围类型的数组,来模拟一个较小的进制,本题中base也可以取100、1000........