關於平方根運算的算法

這段日子,空閒的時候在看SICP(Structure and Interpretation of  Computer Programs),發現滿有意思的。
剛開始就看到了一個求平方根的算法。滿腦子搜尋,好像從小就沒學過如何計算平方根72_72.gif,只知道平方根的定義。於是就打算記錄下來。

用牛頓法求解平方根:

輸入:x
輸出:y
要求:(y * y - x) < 0.001   (注:因爲平方根運算一般都牽涉到一個精度的問題)
算法:
1. 猜測一個值作爲y
2. 如果y的精度不夠,則用如下公式改進y:
    y = (y + x/y) / 2;
    然後重複第二步;
    如果y的精度夠了,就輸出y;

不知道上面的表達是不是夠清楚了。下面給出我的程序吧。

#include <iostream>

using namespace std;

double my_abs(double y)
{
     if(y < 0)
          return (-y);
     else
          return y;
}

bool good_enough(double guess, double x)
{
     return ( my_abs( (guess * guess) - x) < 0.001);
}

double try_sqrt(double guess, double x)
{
     if(good_enough(guess, x))
          return guess;
     else
          return try_sqrt( (guess + (x / guess)) / 2.0, x);
}
double my_sqrt(double x)
{
     return try_sqrt(1.0, x);
}

int main()
{
     cout << my_sqrt(2) << endl;
     return 0;
}

當然,可以看到,這裏對於最初的那個猜測的值,取的是1.0,關於這個初始值的取法,不知道有沒有好的方法。另外還有一個問題就是關於如何判斷是否夠精確了。這裏的算法只是判斷該值的平方與那個x的差值是否小於0.001。還有一種判斷方法就是比較上一次的結果和這次的結果的差值,這個方法與上面的那個方法的差別我還沒考慮過。

最後呢,還有一個就是與標準庫的sqrt()函數的比較問題。
上面那個程序的運行結果如下:
1.41422
而標準庫的結果是:
1.41421
不知道標準庫裏是用什麼算法計算的。

另外,如果把那個判斷精度的0.001改成0.0001,好像結果沒有變化。但如果改成0.01的話,就明顯看出結果的誤差,這個算法的結果是:1.41667。另外,如果算36的平方根的時候,取0.01作爲精度的話,結果就有問題了,這個算法的結果是:6.00025,如果取0.001作爲精度,那結果就正確了,嘻嘻~

等那天有空再去看看標準庫的算法吧,懷疑可能就是判斷精度的問題上有區別。呵呵。

哦,還有一個需要記錄的,就是看SICP上的說法,求平方根只是牛頓法的一個特例,它還有求立方根的算法,其普遍形式好像是一個求解方程的算法,等我看了再作記錄吧。tongue_smile.gif

補充:
沒解決問題的情況下,總覺得心有不甘,於是我就又試了一下,這次取相鄰兩次計算值的差來作爲精度的確定,發現結果就和上面的不一樣了,還是先貼程序:

bool good_enough2(double guess1, double guess2)
{
     if( my_abs( guess1 - guess2) < 0.01)
         return true;
     else
         return false;
}

double try_sqrt2(double guess, double x)
{
     double next_guess = (guess + (x / guess)) / 2.0;
     if(good_enough2(next_guess, guess))
         return next_guess;
     else
         return try_sqrt2( next_guess, x);
}

double my_sqrt2(double x)
{
     return try_sqrt2(1.0, x);
}

注意到上面我用紅色標註的那個0.01了吧?在前一種算法中,需要0.001才能達到正確精度,而這個算法只需要0.01就可以達到精度了。當然,我這個精度比較是通過求36的平方根來算的。第二個算法裏用0.01就能得到正確的值,而用0.1的話,結果就和第一個算法用0.01的結果一樣了。
不過這裏面的緣由似乎也很簡單吧,因爲後面的算法是直接比較結果值,而前者是比較平方後的結果值,這樣誤差也是有類似於平方關係的吧。就比如現在用的0.01的精度,平方後的精度就是0.0001,比原來的0.001的精度還要高,那結果自然正確。而如果取0.1的話,那麼平方之後是0.01,自然就沒有第一種那麼精確了。

現在我通過測試驗證,第二種算法的和標準庫的結果是一致的,就比如前面測試的求2的平方根,第一種算法得出的結果是1.41422,而標準庫的結果都是1.41421,第二種算法當那個精度值取0.01的時候,結果同第一種算法,而取0.001的時候,結果就與標準庫的相同了。

其實想一下也很簡單,第二種算法比較的是結果的值,其精度是直接取決於那個精度值的,因此精度可以較高。而第一種算法的那個精度值是結果平方後的,也就是需要精度更爲高的值(也就是1之前的0應該更多,呵呵),才能達到高精度。實驗證明,第一種算法取0.00001的時候,仍舊是1.41422,而取0.000001的時候,結果就是1.41422了。

好了,平方根算法也研究得差不多了,至少表面樣子上已經差不多了。如果從效率上來說,兩種算法似乎差不多吧。不過第一種算法“平方之後再比較”應該比第二種算法“直接比較結果”來得慢一些吧,畢竟多了一個乘法運算了。由於數學能力和算法分析能力已經不行了,就不計算複雜度了吧red_smile.gif

再補充一下:
剛纔這些結果都是用cout的標準輸出精度來看的,所以精確度也就這點了。我剛纔又找了一下標準庫,發現通過std::setprecision() 可以設置輸出的精度。結果就發現問題了,就是最後還是發現了結果不同。而且我那個0.001不管設得再小,也好像和標準庫的結果不一樣,雖然誤差很小很小,不過這又使得我懷疑標準庫的算法還是有所不同。下面是結果:
1.4142135623730949    算法一
1.4142135623730949    算法二
1.4142135623730951    標準庫的算法
上面這個結果是我已經加了好多0的情況下了(具體多少個我也沒數過,反正好多,呵呵tongue_smile.gif

就先這樣吧。哪天看了標準庫的算法,再做補充吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章