由阶乘问题引发的关于数据溢出的讨论

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

为了对进程递归有更深入的理解,设计了一个计算输入数阶乘的小程序,该程序的最初版本如下(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;
	}
	
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值