有人发了一个帖子问怎么计算20!?
如果没有其他任何深入思考,觉得这个问题实在是很简单。如是下面有这样的回复:
#include <stdio.h>
main()
{
int i;
int s=0;
for(i=1;i <=20;i++)
s+=i;
printf( "s=%d ",s);
}
这个回复当然符合习惯的思维,一个循环不就解决了吗?
这时有人就说了,20!那是一个很大的数,int 类型根本不够,如是有这样的回复:
__int64 Factorial(__int64 n)
{
if(n == 1)
return 1;
else
return n*Factorial(n-1);
}
这个回复已经认识到问题了,至少思考了一下问题了。但是还是有人说了64位也不够的,如是有人这样回复:
你只是想实现 20! 还是想 进行大数运算,若只是想实现20!可以这样:
#include <iostream>
using namespace std;
double factorial(int n)
{
if(n <=1)
return 1;
else
return n*factorial(n-1);
}
void main(void)
{
cout < < "20!= " < <factorial(20) < <endl;
system( "pause ");
}
当然大家知道递归的效率比较低,若考虑到效率,可以改为:
#include <iostream>
using namespace std;
double factorial(int n)
{
double arr[21]={1,1};
for(int i=2;i <=20;i++)
arr[i]=arr[i-1]*i;
return arr[20];
}
void main(void)
{
cout < < "20!= " < <factorial(20) < <endl;
system( "pause ");
}
看到这样的回复,我相信lz应该已经很满足了,至少他的问题得到回答了。但是还是有人这样回复:
#include <stdlib.h>
/*-------任意大数阶乘的实现方法,可实现最大积为M-1位数的阶乘---------*/
#define M 1000
int data[M]; //第一位存储数的长度,从第二位开始依次从低位向高位存储每位数字
int carry[M]; //存储每次乘积的进位
void Inital() //初始化数组data
{
int i;
for( i = 2; i <= M - 1; i++ )
data[i] = 0;
data[0] = 1; //开始只有1位数
data[1] = 1; //0的阶乘为1
}
// 调整的起始位置 进位值 要调整的数组
void adj_carry( int iStartIndex, int iPlus, int array[] )
{
while( array[iStartIndex] + iPlus > = 10 ) //向高位进
{
array[iStartIndex] = (array[iStartIndex] + iPlus) % 10;
iPlus = (array[iStartIndex] + iPlus) / 10;
iStartIndex++;
}
array[iStartIndex] += iPlus;
}
//用当前的数每别乘以data数组里从第二位开始的数,同时完成进位
void goon( int i, int iNum ) //i:当前乘的位数 M-1> = i > =1; iNum:当前的阶乘子
{
int iPlus;
int iTotal = data[i] * iNum; //当前的总数
data[i] = iTotal%10;
if( iTotal > = 10 )
{
adj_carry( i+1, iTotal/10, carry ); //调整进位数组
}
}
//调整阶乘位数
void adj_total( )
{
int i;
for( i = M - 1; i > = data[0]; i-- )
if( data[i] > 0 )
{
data[0] = i; //共有i位
break;
}
}
//将进位加至和数数组
void add_carry_to_data()
{
int i;
int iTotal;
for( i = 1; i <= M - 1; i++ )
{
iTotal = data[i] + carry[i];
data[i] = iTotal % 10;
if( iTotal > = 10 ) //进位
{
adj_carry( i+1, iTotal/10, data ); //调整进位数组
}
}
}
//进位数组清0
void clear_carry()
{
int i;
for( i = 1; i <= M - 1; i++ )
carry[i] = 0;
}
void Mul( int iNum ) //阶乘
{
int i, j;
for( i = 1; i <= iNum; i++ )
{
for( j = 1; j <= data[0]; j++ )
goon( j, i );
add_carry_to_data(); //将进位数组加至和数组
adj_total( ); //调整总位数
clear_carry(); //清0进位数组,准备下一次阶乘
}
}
main()
{
int i;
Inital();
Mul( 100 );
printf( "共 %d 位数:/n ", data[0] );
for( i = data[0]; i > = 1; i-- )
printf( "%d ", data[i] );
printf( "%/n " );
system( "pause ");
}
结果:
共 154 位数:
50030657837459613088140586563373203306192310297549125085554140803486718798911936
93461999118240301448181697299177053424912511152960000000000000000000000000
请按任意键继续. . .
这样的回复当然是比较全面的,至少比前面几位的回复要好。
这个帖子给我的思考是如果把lz的问题当做一个需求,下面的回复都是一种设计方案,那么很明显前面两种方案都是没有实现需求,第三种虽然满足了需求,但是扩展性和健壮性都不够,第四种方案在这些方面都有所提高但是还不够。反过来说,当看到这样的需求的时候,我们测试如何去预见可能发生的错误呢?怎么防止像回复1和2那样错误的方案,更如何去预防像方案3那样正确但不完善的方案呢?
我觉得以下几件事情可以去做:
1.分析需求隐藏的需求。针对lz的提问,问一个问题。一个是20!的阶乘的结果是多少位数?另一个是只是算20!还是也可能要算更大的数?当然除了这两个问题以外还可以问,输入的都是数字吗?还是有可能会输入其他字符呢?
2.猜想可能存在的简单设计。按照lz的提问,最可能的设想便是递归或者循环。那么有了这种猜想,可以至少防止1,2方案的发生。
3.挖掘需求潜在的需求。只是算20!还是也可能要算更大的数?当然除了这两个问题以外还可以问,输入的都是数字吗?还是有可能会输入其他字符呢?