为了对进程递归有更深入的理解,设计了一个计算输入数阶乘的小程序,该程序的最初版本如下(V1.0):
//version: 1.0
#include <stdio.h>
#include <stdlib.h>
long factorial(int n)
{
if(n == 0)
return 1;
if(n < 0)
{
printf("error\n");
return -1;
}
else
return n * factorial(n - 1);
}
int main(int argc, char** argv)
{
int n = 0;
if(argc != 2)
{
printf("Input format should be: ./factorial num\n");
return 0;
}
n = atoi(argv[1]);
if(factorial(n) > 0)
printf("%d! = %ld\n", n, factorial(n));
return 1;
}
V1.0看似没有问题,但在实际运行时出现了明显的错误。在计算至21!时,没有输出值。进而计算21之后的整数的阶乘,发现输出结果时有时无。
于是使用GDB调试逐步寻找,发现在计算21! 时,得到的factorial(21)值为负数,在第30行的if判断中得到假,进而跳过输出步骤。
根据调试得到的信息,可推测是由于数据溢出所致。用于记录阶乘结果的数据类型使用的是long型。在long型数据的比特表示中,最高位为0则为正数,为1则为负数,后面的63位用于记录数据的绝对值。在该程序执行过程中,由于阶乘增长速度很快,在计算至21!就已将long型数据的后63位占满,溢出至符号表示位。当factorial(n)的符号位为1时,值为负,所以根据算法没有输出。
根据已知错误的原因,对V1.0进行更改。将记录factorial(n)的数据类型改为unsigned long型(64位全部记录数字的值,没有正负区别),并删去判断factorial(n)正负的步骤。具体代码修改如下:
1. 第5行 long factorial(int n) 更改为 unsigned long factorial(int n)
2. 删去第30行 if(factorial(n) > 0)判断,直接输出结果
3. 原第31行printf中的 %ld 更改为 %lu
修改得到V2.0版本,解决了V1.0中遇到的问题,但在进一步验证中出现了非常特殊的情况,当计算大于等于66的整数的阶乘时,输出值恒为0。
输出值为0表明factorial(66)的数据中64位全部为0。于是提出推测:由于阶乘结果过大,数据溢出过多,导致64位表示中末尾的0的数量不断增加。在计算至66!时,刚好累加至64个0,导致输出结果为0。根据该程序阶乘的算法,在计算66之后的整数阶乘时会使用到66! = 0的结果,导致其输出结果全部为0.
为了验证上述推测,再次修改程序,在计算阶乘时将小于该数的正整数的阶乘依次输出,并且在十进制的表示结果后添加64位的二进制表示来展示factorial(n)的内部数据表示。编写一个函数tobin(unsigned long x)来实现64位二进制输出。用于测试的新文件FactorialTest如下:
//test version
#include <stdio.h>
#include <stdlib.h>
unsigned long factorial(int n)
{
if(n == 0)
return 1;
if(n < 0)
{
printf("error\n");
return -1;
}

探讨了在C语言中使用不同数据类型计算阶乘时遇到的数据溢出问题,通过逐步调试和修改代码,揭示了数据类型限制对计算结果的影响。
最低0.47元/天 解锁文章
1353

被折叠的 条评论
为什么被折叠?



