首页 > 代码库 > 一个由IsPrime算法引发的细节问题
一个由IsPrime算法引发的细节问题
//*******************************
//
// 2014年9月18日星期四,于宿舍撰写
// 作者:夏华林
//
//********************************
好久没有没有更新博客了,最近确实烦心事儿挺多,已经大三了,真的静下心来好好看看书了。
今天要说的,就是一个由IsPrime算法引发的细节问题,我这里说的细节,是我所认为的,若有不妥,望指正!
一个简单的IsPrime算法的实现如下:
1 bool IsPrime(int n) 2 { 3 int i; 4 5 if(n % 2 == 0)return false; 6 for(i = 3; i <= sqrt(n); i += 2) 7 { 8 if(n%i == 0)return false; 9 }10 return true;11 }
这段代码中有一些严重的错误,很明显的就是,当参数为1和2的时候,函数就会返回一个错误的答案,要解决这个问题,最简单的方法就是单独检查1和2,可以在函数的开头简单的加入:
if(n <= 1)return false;if(n == 2)return true;
还有一个性能上的问题,IsPrime算法的本意是为了提高效率,但实际情况下,有时候却会比老的算法花的时间更长。
这种问题存在于for循环的控制行中:
for(i = 3; i <= sqrt(n); i += 2)
尽管现代计算机能在相当短的时间内计算平方根,但,计算平方根还是会比执行简单的算术运算要花的时间要长。程序中,每执行一次循环,都要计算一下sqrt(n),而n在整个循环中都是不变的量,那么我们每次循环都计算一下sqrt(n)就显得不那么划算,为了避免一次次的调用sqrt()函数,可以在循环前先计算出sqrt(n),把它存入一个变量,例如:
double limit = sqrt(n);for(i = 3; i <= limit; i += 2)
这个简单的改变,将明显的改善了IsPrime算法的实现效率。
这个IsPrime算法还有一个很难查出的问题,发现这个逻辑错误是很难的,因为它可能在你成千上万的测试例子中都不会出现,而对于一些特殊的测试例子,这个实现可能会在一些机器上能得出正确结果,而在另一个机器上得出不正确的答案。
为了理解这个问题,我觉得自己还是有必要再补写一篇关于计算机浮点数相关的文章,但限于篇幅,这里就不详细叙述其原理了
对浮点数判断严格的相等,是一个危险的行为。假设n是49,它是7的平方,当计算机对49调用sqrt()函数时,会返回什么?在严格的数学领域,这个平方根是7,但计算机并不是在这个领域内运作的,它返回的仅仅是一个接近7的浮点数,而这个数可能是6.9999999999999999999,尽管这个数很接近7,但这个差别足以影响i<=limit的结果。如果i是7,而limit是6.999999999,则循环的最后一个周期将不会得到执行,程序永远不会检查到是否可以整除7,而7又是49唯一的质因子,这样程序会错误的将49分类素数。另一方面,如果sqrt(49)返回的是7.0或者7.0000000001,那么IsPrime算法将会得到正确的答案。因而,这个实现的正确性居然要取决于硬件是如何执行浮点数运算的,而让一个算法的正确性依赖于运行它的计算机的特性是一个严重的错误。这个问题很容易解决,如果n的平方根小于某一个界限,为了保险起见,我们总倾向于多检查一个可能的约数,多测试一个约数并不会有什么害处,仅仅是付出一个非常小的代价,就能确保算法能在不同硬件上得到正确性的执行,这样的取舍对于我们来说是相当合算的
只要简单的修改:
double limit = sqrt(n) + 1;
IsPrime算法本身是一个非常简单的,但其中的细节却值得我们引起足够的重视。
一个由IsPrime算法引发的细节问题