我有以下功能:
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.Pow
将BigInteger
作为第一个参数,因此我无法做出选择,只能将Math.E
包裹到BigInteger
。
然而,通过这样做,我实际上截断了我的Math.E
的小数部分,这会破坏我的算法,因为我最终得到了完全不同的结果。
我正在寻找类似BigDouble
之类的东西,但我找到的唯一一个类是BigInteger
。
如何在此函数上成功返回正确的BigInteger
结果,同时在收到大输入时有异常安全的代码?
答案 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这样的数字上获得相当大的加速。那就是那个 缩放所有函数真的慢。