2011-02-01 61 views
10

我被實現算法中C.雙等於0問題用C

double taylor_ln(int z) { 
    double sum = 0.0; 
    double tmp = 1.0; 

    int i = 1; 
    while(tmp != 0.0) { 
     tmp = (1.0/i) * (pow(((z - 1.0)/(z + 1.0)), i)); 
     printf("(1.0/%d) * (pow(((%d - 1.0)/(%d + 1.0)), %d)) = %f\n", i, z, z, i, tmp); 
     sum += tmp; 
     i += 2; 
    } 

    return sum * 2; 
} 

來計算自然對數。如圖所示由打印語句,TMP確實等於0.0最終,然而,循環繼續。什麼可能導致這個?

我在Fedora 14 AMD64和與編譯:

clang -lm -o taylor_ln taylor_ln.c 

例子:

$ ./taylor_ln 2 
(1.0/1) * (pow(((2 - 1.0)/(2 + 1.0)), 1)) = 0.333333 
(1.0/3) * (pow(((2 - 1.0)/(2 + 1.0)), 3)) = 0.
(1.0/5) * (pow(((2 - 1.0)/(2 + 1.0)), 5)) = 0.000823 
(1.0/7) * (pow(((2 - 1.0)/(2 + 1.0)), 7)) = 0.000065 
(1.0/9) * (pow(((2 - 1.0)/(2 + 1.0)), 9)) = 0.000006 
(1.0/11) * (pow(((2 - 1.0)/(2 + 1.0)), 11)) = 0.000001 
(1.0/13) * (pow(((2 - 1.0)/(2 + 1.0)), 13)) = 0.000000 
(1.0/15) * (pow(((2 - 1.0)/(2 + 1.0)), 15)) = 0.000000 
(1.0/17) * (pow(((2 - 1.0)/(2 + 1.0)), 17)) = 0.000000 
(1.0/19) * (pow(((2 - 1.0)/(2 + 1.0)), 19)) = 0.000000 
(1.0/21) * (pow(((2 - 1.0)/(2 + 1.0)), 21)) = 0.000000 
and so on... 
+0

哇,四個人在同一時間有相同的答案。 – mgiuca 2011-02-01 03:24:36

+0

看看:http://stackoverflow.com/questions/4664662/understanding-floating-point-problems/4664784。如果你不瞭解它們,浮點數可能非常棘手。 – 2011-02-01 03:25:50

回答

9

浮點比較確切足夠小,所以10^-10是不一樣的0.0

基本上,你應該比較對一些容許差別,說10^-7根據你寫出來的小數位數,可以實現爲:

while(fabs(tmp) > 10e-7) 
1

浮點數處理時,不要使用精確的相等操作。雖然你的號碼可能看起來0,它可能是類似於0.00000000000000000000001

如果在格式化字符串中使用%.50f而不是%f,則會看到此內容。後者對小數位使用了一個合理的默認值(在你的情況下是6),但前者明確指出你需要很多。

爲安全起見,使用增量,以檢查它是否足夠接近,例如:

if (fabs (val) < 0.0001) { 
    // close enough. 
} 

顯然,增量完全取決於你的需求。如果你說錢,10 -5可能會很多。如果你是物理學家,你應該選擇一個更小的值。

當然,如果你是一個數學家,沒有任何不準確是:-)

0

僅僅因爲一個號碼顯示爲「0.000000 「並不意味着它等於0.0。數字的小數顯示比雙精度可以存儲的精度低。

您的算法有可能達到非常接近0的點,但下一步的移動很小,以至於它與之前的相同,因此它永遠不會接近0 (剛進入無限循環)。

通常,您不應該將浮點數與==!=進行比較。您應該始終檢查它們是否在某個小範圍內(通常稱爲epsilon)。例如:

while(fabs(tmp) >= 0.0001) 

然後,當它變得相當接近於0

0

print語句顯示舍入值就會停止,這是不打印儘可能高的精度。所以你的循環還沒有真正達到零。

(而且,正如其他人所說,由於四捨五入問題,它實際上可能永遠也做不到。價值對一個小的限制比較,因此比平等0.0相比更加強勁。)的討論

0

充足原因,但這裏是一個替代的解決方案:

double taylor_ln(int z) 
{ 
    double sum = 0.0; 
    double tmp, old_sum; 
    int i = 1; 
    do 
    { 
     old_sum = sum; 
     tmp = (1.0/i) * (pow(((z - 1.0)/(z + 1.0)), i)); 
     printf("(1.0/%d) * (pow(((%d - 1.0)/(%d + 1.0)), %d)) = %f\n", 
       i, z, z, i, tmp); 
     sum += tmp; 
     i += 2; 
    } while (sum != old_sum); 
    return sum * 2; 
} 

這種方法側重於TMP的每個下降值是否能留下實實在在的區別總結。這比從tmp變得無關緊要的0計算出一些閾值要容易得多,並且可能在不改變結果的情況下提前終止。

請注意,當您將相對較大的數字與相對較小的數字相加時,結果中的有效數字會限制精度。相比之下,如果你總結幾個小的那個,然後把它加到大的那個,那麼你可能有足夠的把那個大的一個碰到一個。在你的算法中,小的tmp值並不是相互累加的,所以沒有累積,除非每個值實際上影響和 - 因此上述方法沒有進一步降低精度。