这是一个相当理论化的问题,所以虽然语言是专门的Java,但任何通用解决方案都足够了。
假设我想编写一个简单的阶乘函数:
long factorial(int n)
{
//handle special cases like negatives, etc.
long p = 1;
for(int i = 1; i <= n; i++)
{
p = p * n;
}
return p;
}
但是现在,我还要检查阶乘是否溢出(不需要简单地编写MAX_FACTORIAL_PARAMETER或类似的东西)。通常,在乘法过程中检查溢出就像检查原始输入的结果一样简单,但在这种情况下,由于溢出可能在任何点发生,因此在每个循环中执行更多的除法和比较会相当昂贵。
那么问题是双重的 - 有没有办法解决溢出的阶乘问题而不检查每一步的乘法溢出或硬编码最大允许参数?
总的来说,我应该如何解决涉及迭代/递归的多个阶段的问题,这些阶段可能会在每个阶段无声地失败而不会通过在每个阶段引入昂贵的检查来影响性能?
答案 0 :(得分:1)
这取决于您的具体问题。也许你可以做曲线草图或其他一些数学分析。
在您给出的示例中,最好只检查每个循环。它不是那么耗时,因为它甚至不会修改你的复杂性类(因为O(n)= O(n + n)= O(2n)= O(n))。在大多数情况下,进行简单检查也是最好的选择,因为它可以保持代码清洁和可维护。
答案 1 :(得分:1)
虽然Java对此没有帮助,但肯定有一些语言可以帮助解决溢出问题。例如,C#提供checked keyword。在下面,此功能可能使用overflow flag形式的硬件支持。
答案 2 :(得分:1)
在这种特定情况下,最简单的事情是硬编码最大'n'值。如果速度对您很重要,您可以将所有可能的值存储在一个数组中(并不多)并且不计算任何值。 ;)
如果要改进方法,请使用long
结果(不是更好但只是简单的更改)或使用没有溢出问题的BigInteger。 ;)
答案 3 :(得分:0)
有没有办法解决溢出的阶乘问题而不检查每一步的乘法溢出或硬编码最大允许参数?
没有
总的来说,我应该如何解决涉及迭代/递归的多个阶段的问题,这些阶段可能会在每个阶段无声地失败而不会通过在每个阶段引入昂贵的检查来影响性能?
我认为你不能用Java。
如果先前的整数算术运算溢出,某些机器指令集会设置'oveflow'位,但大多数编程语言都没有提供使用它的方法。 C#是一个例外,(IIRC)Ada。
答案 4 :(得分:0)
从Java 8开始,Java具有Math.multiplyExact()
方法。这会在内部检查溢出。由于该方法是'内在的'(see here for a list),如果可能的话,Java实现将被专用的低级机器指令替换,可能只是检查CPU溢出标志,从而使得检查非常快。
顺便说一下,请注意,与JDK 1.0.8_05相比,JDK 1.8.0_40上的速度似乎要快得多。
答案 5 :(得分:-1)
在Java中,溢出应该给你一个否定的结果,所以你可以用它作为一个检查:
long factorial(int n)
{
//handle special cases like negatives, etc.
long p = 1;
for(int i = 1; i <= n; i++)
{
p = p * n;
}
if (p < 0)
{
throw new ArithmeticException("factorial: Overflow");
}
return p;
}
或者,对于具有正溢出的语言,因为n! &GT; (n - 1)!你可以尝试:
long factorial(int n)
{
//handle special cases like negatives, etc.
if (n == 1 || n == 2) { return n; }
// Calculate (n-1)!
long p = 2;
for(int i = 3; i < n; i++) // Note changes to loop start and end.
{
p = p * n;
}
long previous = p; // (n-1)!
p = p * n; // Calculate n!
if (p < previous)
{
throw new ArithmeticException("factorial: Overflow");
}
return p;
}
这些方法每个因子只需要检查一次。