#include <stdio.h>
#include <math.h>
int main()
{int n,i,k;
printf("please enter a integer number,n=?");
scanf("%d",&n);
k=sqrt(n);
for(i=2;i<=k;i++)
if(n%i==0)break;
if(i<=k)printf("%d is not a prime number.\n",n);
else printf("%d is a prime number.\n",n);
return 0;
}
————譚浩強.《C程序設計》(第四版).p137,清華大學出版社
這個程序的代碼首先輸入需要判斷的整數並存放在變量n中(其實這個變量定義為unsigned類型更理想,因為題目中已經明確是大於3的整數)。緊接着通過:
k=sqrt(n);
試圖求出n的平方根或其平方根的整數部分。然而這個寫法是有問題的,能否正確地按照希望求得n的平方根或其整數部分是得不到保證的。
這是因為sqrt()函數的函數原型是:
double sqrt (double);
也就是說,sqrt()函數的參數和返回值都是double數據類型。double類型屬於浮點數據類型的一種,浮點類型並非像整數類型那樣可以精確地表示整數,正相反,浮點數據類型用於近似地表示實數,盡管在個別情況下可以精確地表示,但就一般意義上而言,浮點數據類型只是對一定范圍內的實數的一種近似表示。
因此,在k=sqrt(n)這個表達式中,不可能指望這個n是准確的,因為按照sqrt()函數原型的要求,這個n實際上表示的是(double)n,即調用時會存在類型轉換,轉換后的值為double類型,它是否精確等於原來的int類型的n值,一般而言是不清楚的。
或許有些對IEEE754標准很熟悉的人對此不能贊同——他們非常清楚在這個標准下浮點數的表示方法和內部結構。好吧,“不爭論”,繼續看下一個不確定性。
由於sqrt()函數的返回值是double類型,這表明sqrt()函數只承諾為我們求得實參的平方根的近似值——而不是像數學那樣一定可以得到一個精確值。換句話說,當調用sqrt(9.)的時候,函數未必會精確地得到3.0這個數學上的精確的平方根,sqrt(9.)的值無論為3.000000000000001還是2.999999999999999 都有可能。這雖然是出於理性的判斷,但也並非絕無經驗事實作為佐證。試看下面的代碼:
#include <stdio.h> #include <math.h> int main( void ) { int p , n = 4 ; p = pow( 10 , n ); printf("%d\n", p ); return 0; }
在某些編譯器上的輸出結果是:
9999
這清楚地表明pow()這樣的返回值為double類型的數學函數只是一種近似計算的函數。sqrt()這個數學函數也是如此,sqrt(9.)的值無論為3.0、3.000000000000001還是2.999999999999999都有可能。一旦sqrt(9.)的值為2.999999999999999,那么
k=sqrt(n);
將使k被賦值為2而不是預期中的3。這樣代碼中的下一句:
for(i=2;i<=k;i++)
的循環次數就會產生錯誤。這種錯誤在何時出現很難預料,但k=sqrt(n)是一個似是而非的表達則是確切無疑的。
這種未必立刻發作的錯誤比那些立刻就產生症狀的BUG更可怕,它就像一顆不定時炸彈一樣說不定什么時候造成損失。古代有則守株待兔的寓言,說的是某一天出現了兔子,你不能指望天天出現兔子。但這里的情形恰恰相反,盡管很多天都沒出現兔子,但你保不准哪天就會突然竄出一只兔子。而一旦出現兔子,其嚴重后果則是你假定不會出現兔子時所無法預料的。
那么此時應該如何求n的平方根或其整數部分呢?實際上可以利用下面的數學常識輕易求得:
1=1^2
1+3=2^2
1+3+5=3^2
…
1+3+5+…+(2k1)=k^2
后面正確的代碼應用了這個原理。
樣本代碼中的另一個問題是根本沒有考慮輸入一旦不小於3時應該如何處理,這樣的一個嚴重后果是一旦用戶誤輸入數據,比如輸入了負數,程序將會發生悲慘的崩潰。在一個真正的軟件中,絕對不能假設用戶一定會正確地輸入,否則可能帶來非常嚴重且無法彌補的損失。
此外,代碼中的輸出部分過於啰唆繁復,后面的代碼對此也進行了修正。
#include <stdlib.h> int main( void ) { int n ; printf("請輸入n的值\n"); scanf("%d",&n); if( n <= 3) printf("輸入不正確,程序退出\n"); else { int n_ = n , odd = 1 , k = 0 , i ; while( n_ >= odd ) { n_ -= odd ; odd += 2 ; k ++ ; } for(i=2;i<=k;i++) if(n%i==0) break; printf("%d%s是素數\n" , n , (i > k)?"":"不"); } return 0; }
其中:
while( n_ >= odd ) { n_ -= odd ; odd += 2 ; k ++ ; }
用於求出n的平方根的整數部分。由於只需進行√n次整數的加減法運算,其效率方面也必定高於浮點運算的k=sqrt(n)。