为啥这个 BigInteger 值会导致堆栈溢出异常? C#

Posted

技术标签:

【中文标题】为啥这个 BigInteger 值会导致堆栈溢出异常? C#【英文标题】:Why does this BigInteger value cause a stack overflow exception? C#为什么这个 BigInteger 值会导致堆栈溢出异常? C# 【发布时间】:2016-02-24 06:45:17 【问题描述】:

我在C# 中使用BigInteger 与阶乘函数相关。该程序具有闪电般的快速计算 5000!,但在 10000! 处有溢出错误。根据wolfram alpha,10000!大约是

10000! = 2.8 x 10^35659

据我从this post 得知,BigInteger 存储在int[] 数组中。如果我正确解释 int 类型,它使用 4 个字节,这意味着 10000!使用大约4 x log10(2.8 x 10^35659) = 142636 字节,其中我使用log10(n)(以10 为底的日志)作为n 位数的近似值。这只有 143 MB,但我仍然得到堆栈溢出异常。为什么会这样?

using System;
using System.Numerics;

class Program

    static void Main()
    
        BigInteger hugeFactorial = Calculations.Factorial(5000);
    


class Calculations

    public static BigInteger Factorial(int n)
    
        if (n == 1) return n;
        else return n*Factorial(n - 1);
    

【问题讨论】:

堆栈溢出异常吧? @IvanStoev 是的,堆栈溢出异常。它说:“发生了'System.***Exception'类型的未处理异常”让我更正我的帖子。 你刚刚回答了你的问题。您是否在异常中看到“BigInteger”一词?你看到“堆栈”这个词了吗?哪个代码在使用堆栈? x64 抖动出错,该方法需要 208 字节的堆栈空间。 5000 x 208 == kaboom,堆栈大小为 1MB。您必须编写一个迭代版本,而不是您现在拥有的递归版本。一个 for(;;) 循环。 我查看了机器码。 208 特定于 RyuJIT,VS2015 包含的 x64 抖动。它使用 AVX 指令,使其更需要堆栈。否则一个细节,代码可以通过让参数足够大来任意轰炸。 【参考方案1】:

线程的默认堆栈大小为 1 MB。您可以在创建新线程时更改它。我会把你的代码写成(不阻塞调用线程):

TaskCompletionSource<BigInteger> tcs = new TaskCompletionSource<BigInteger>();
var t = new Thread(() => 
    
        var res = Calculations.Factorial(10000);
        tcs.SetResult(res);
    , 
    1024*1024*16 //16MB stack size
);
t.Start();
var result = await tcs.Task;
Console.Write(result);

【讨论】:

【参考方案2】:

正如loopedcode 所说,您应该至少使用迭代算法来计算阶乘。

public static BigInteger Factorial(int n)

    BigInteger result = 1;
    for (int i = 2; i <= n; i++)
    
        result *= i;
    
    return result;

还有更高效的算法 (look here)。

【讨论】:

【参考方案3】:

Factorial 的递归调用会导致堆栈溢出,以获得足够大的调用堆栈。你的电话10000!很可能达到这个目标。您可能必须将实现更改为迭代算法以修复溢出问题。

【讨论】:

好的,谢谢。我不知道这个“调用堆栈”,但据我了解,调用堆栈上的绑定不是我计算机的某些固有属性,而是我可以更改的 C# 中的参数。

以上是关于为啥这个 BigInteger 值会导致堆栈溢出异常? C#的主要内容,如果未能解决你的问题,请参考以下文章

为啥增加递归深度会导致堆栈溢出错误?

为啥我的代码会导致 java 中的堆栈溢出错误?它最终应该终止。 for循环版本不会导致错误

导致堆栈溢出的最短代码是啥? [关闭]

为啥无限递归会导致段错误

为啥将 <%= %> 表达式作为服务器控件上的属性值会导致编译错误?

BigDecimal为啥可以精确计算