如何从Math.Pow返回BigInteger

时间:2015-08-28 23:56:02

标签: c# math biginteger

我有以下功能:

private static BigInteger Factorial(int number)
{
     if(number < 2) return BigInteger.One;
     double sum = 0;
     for(int i = 2; i < number; i++)
         sum += Math.Log(i);
     return new BigInteger(Math.Exp(sum));
}

这适用于10之类的小输入数字,但是如果我传递更大的数字,例如50000,它会崩溃并抛出OverflowException

我理解为什么会发生这种情况,Math.Exp(sum)的结果对于double来说太大了。但这就是我尝试使用BigInteger的原因,以避免这些类型的例外。

问题是,将new BigInteger(Math.Exp(sum))这样的结果包装起来是没用的,因为Map.Exp(sum)无论如何都试图返回double

所以我决定使用BigInteger.Pow静态函数:

return BigInteger.Pow(new BigInteger(Math.E), number);

请注意new BigInteger(Math.E)部分。 BigInteger.PowBigInteger作为第一个参数,因此我无法做出选择,只能将Math.E包裹到BigInteger

然而,通过这样做,我实际上截断了我的Math.E的小数部分,这会破坏我的算法,因为我最终得到了完全不同的结果。

我正在寻找类似BigDouble之类的东西,但我找到的唯一一个类是BigInteger

如何在此函数上成功返回正确的BigInteger结果,同时在收到大输入时有异常安全的代码?

1 个答案:

答案 0 :(得分:1)

您可以使用base 2而不是base e

private static BigInteger FactorialEstimate(int number)
{
    if (number < 2) return BigInteger.One;
    double sum = 0;
    for (int i = 2; i <= number; i++)
        sum += Math.Log(i, 2);
    return BigInteger.Pow(2, (int)Math.Round(sum));
}

如果您需要更准确的答案:

private static BigInteger NthRoot(BigInteger a, int n)
{
    BigInteger min = BigInteger.Zero, max = a, mid = a;
    while (min < max)
    {
        mid = (min + max) >> 1;
        if (BigInteger.Pow(mid, n) < a)
            min = mid + 1;
        else
            max = mid;
    }
    return max;
}

const int Accuracy1 = 16;
const int Accuracy2 = 8;
private static BigInteger FactorialBetterEstimate(int number)
{
    if (number < 2) return BigInteger.One;
    double sumFractPart = 0;
    int sumIntPart = 0, tmpIntPart = 0;
    for (int i = 2; i <= number; i++)
    {
        sumFractPart += Math.Log(i, 2);
        tmpIntPart = (int)sumFractPart;
        sumFractPart -= tmpIntPart;
        sumIntPart += tmpIntPart;
    }

    int correction = Math.Min(Accuracy2, sumIntPart);
    sumIntPart -= correction;
    sumFractPart += correction;

    return NthRoot(BigInteger.Pow(2, Convert.ToInt32(Accuracy1 * sumFractPart)), Accuracy1) << sumIntPart;
}

但是,与精确的阶乘函数相比,您只能在100000这样的数字上获得相当大的加速。那就是那个 缩放所有函数真的慢。