计算阶乘的C程序

3

我写了一个小函数,用C语言计算一个数的阶乘,代码如下:

int factNnumbers(int n)
{
    if(n == 1)
        return 1;
    else
        return (n*factNnumbers(--n));
}

我将上述函数称为:

factNnumbers(takeInputN());

输入函数(takeInputN)的定义如下:

int takeInputN()
{   
    int n;
    printf("\n\nHow many numbers ?? \n ");
    scanf("%d", &n);
    return n;
}

如果我按照下面所示更改我的阶乘代码中的一行,那么我的程序就可以完美地工作。否则,使用上述代码,它将打印输入数字-1的阶乘(例如,如果输入数字为5,它将打印4的阶乘)。为什么会发生这种情况?
int factNnumbers(int n)
{
    if(n != 1)
        return (n * factNnumbers(--n));
}

2
你的第二个版本甚至更错误。 - Giorgi Moniava
6个回答

6

您正在调用未定义的行为,它在一个版本中工作只是偶然事件:

return (n*factNnumbers(--n));

你是先使用n再递减它,还是反过来呢?我不知道,编译器也不知道,它可以自由地选择其中任何一个或格式化你的硬盘。只需使用n * f(n - 1)

此外,你的“工作”版本没有为n==1的情况返回,这是不合法的。


3
我不知道谁在这里给东西投负票,但这是正确的答案。 - Jens Gustedt
1
同样的,终止测试必须是if(n<=1) return 1;因为0!等于1 - Weather Vane

6
问题在于你在同一个表达式中既读取了n,又修改了它:
n * factNumbers(--n)

评估 *n 参数和 --n 子表达式是未排序的,这会导致您的代码出现未定义行为。
最简单的解决方案(也更具表达性)是在需要时使用 n - 1
n * factNumbers(n - 1)

在问题底部的你提供的“改进”的代码实际上更加错误。在那里,你有一条控制路径会返回未指定的值:这是一个严重问题。


注意:这个回答是在问题还带有C++标签的情况下编写的,并使用了C++术语。在C语言中的最终效果是相同的,但术语可能不同。


这是因为在没有序列点的子表达式之间评估顺序是未指定的。 - Giorgi Moniava
1
@GiorgiMoniava 是的,这是子表达式求值顺序未指定的一般情况。只有少数明确的例外规定了顺序(函数调用、逗号运算符、逻辑运算符等)。 - Angew is no longer proud of SO
有道理。第二个版本是我在这里随机看到的,然后我尝试了一下。虽然它似乎可以工作。感谢您的快速回复! - abhinavm93
另一件在我腦海中的事情,希望我的問題表達清楚。當我調試代碼時,我注意到該函數遞歸調用了5次(如果我沒有錯的話,這意味著在內存中構建了5個堆棧),所以我猜測在初始堆棧中,值開始於--n而不是n,並計算了--n的階乘?只是好奇。 - abhinavm93
@abhinavm93 未定义行为是未定义的。 你在调试器中观察到了一个特定的执行过程,但是如果执行过程改为调用 system("sudo rm -rf /"),那么至少在标准方面来说也是有效的。对于UB的推理可能是关于特定编译器及其优化器实现细节的好练习,但仅此而已。 - Angew is no longer proud of SO

2
您的代码存在两个未定义行为的原因:
  1. n * factNnumbers(--n) 中,无法确定是先计算 n 还是 --n
    参见这里。您只需要使用 n * factNnumbers(n - 1),为什么要进行递减操作?之后您并没有使用递减后的 n (至少您不想这样做)。

  2. 您并未在所有控制路径上返回一个值,在 n == 1 的情况下会返回什么?将返回一个无法确定的值,会破坏整个结果。


不,它并不只是未指定的。在同一表达式中访问已更改的值是不允许的,并会导致未定义的行为。 - Jens Gustedt
@Angew,这可能是正确的,但它是不完整的。在同一表达式中访问被更改的对象是一个严重的编程错误。 - Jens Gustedt
@Angew 你说的未指定值是未定义行为是什么意思? - Giorgi Moniava
@GiorgiMoniava 其实,我并没有完全正确,至少在 C++14 术语上是如此。评估是无序的,而不是未指定的顺序;这样的无序多次使用被明确称为未定义行为。 - Angew is no longer proud of SO
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Giorgi Moniava
@Angew C++标签已被删除。 - LogicStuff

0

递减规则: X = Y-- 首先将 I 的值传递给 X,然后再递减 X = --I 首先递减值,然后将递减后的值传递给 X

对于您的情况,您递减了作为参数传递给函数 factNnumbers 的值。因此,为了纠正这个错误,我建议您将 (--n) 替换为 (n-1)。


0

我认为在涉及阶乘(如泊松分布等)的双精度计算中,最好使用math.h中的tgamma函数:tgamma(n+1)将给出factorial(n)。

阶乘函数增长非常快,这种方法也适用于值太大而无法适应整数类型的情况。


-1
当n≤1(或在您的代码中等于1)时,阶乘函数必须返回1。此外,您代码中的--n是错误的,应该改为n-1:
function factorial(n)
{
   if (n<=1)
      return 1;
   else
      return n * factorial(n-1);
}

这并没有回答问题,问题是“为什么会发生这种情况?”你提供的是替换代码,而不是原因。 - Angew is no longer proud of SO
1
这仍然是正确的。难道楼主不能从正确的版本中推理吗?在我看来,这并不值得被踩。 - duffymo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接