似是而非的k=sqrt(n)


//題目:輸入一個大於3的整數n,判定它是否為素數(prime,又稱質數)
#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+…+(2k1)=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)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM