2011-02-01 30 views
10

tôi đã thực hiện một thuật toán để tính toán các bản ghi trong tự nhiên C.đúp bằng 0 vấn đề trong 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; 
} 

Như đã trình bày bởi báo cáo kết quả in ấn, tmp làm bằng 0.0 cuối cùng, tuy nhiên, vòng lặp tiếp tục. Điều gì có thể gây ra điều này?

Tôi đang trên Fedora 14 amd64 và biên soạn với:

clang -lm -o taylor_ln taylor_ln.c 

Ví dụ:

$ ./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

Chà, bốn người có cùng câu trả lời cùng một lúc. – mgiuca

+0

Hãy xem: http://stackoverflow.com/questions/4664662/understanding-floating-point-problems/4664784. Số điểm nổi có thể rất phức tạp nếu bạn không biết rõ chúng. –

+0

@mgiuca: sau đó nó _must_ được quyền :-) – paxdiablo

Trả lời

9

Việc so sánh dấu chấm động là chính xác, vì vậy 10^-10 là không giống như 0.0.

Về cơ bản, bạn nên so sánh với một số khác biệt chấp nhận được, nói 10^-7 dựa trên số thập phân bạn đang viết ra, mà có thể được thực hiện như sau:

while(fabs(tmp) > 10e-7) 
+5

Và đây là liên kết bắt buộc: [Mỗi nhà khoa học máy tính nên biết gì về số học dấu chấm động] (http://citeseer.ist.psu.edu/viewdoc/download;jsessionid=86013D0FEFFA6CD1A626176C5D4EF9E2?doi=10.1.1.102.244&rep= rep1 & type = pdf) – chrisaycock

+0

Hai điều: abs là một hoạt động số nguyên; sử dụng 'fabs'. Và bạn muốn trong khi * lớn hơn * một số ngưỡng, không nhỏ hơn. – mgiuca

+0

@chrisaycock: Bạn nên làm một câu trả lời ... đó là (hoặc phiên bản đơn giản) là câu trả lời đúng cho hầu hết các câu hỏi liên quan đến dấu phẩy động quanh đây. –

1

Không sử dụng các hoạt động bình đẳng chính xác khi giao dịch với số dấu chấm động. Mặc dù số điện thoại của bạn có thể là trông như 0, nhưng có thể giống như số 0.00000000000000000000001.

Bạn sẽ thấy điều này nếu bạn sử dụng %.50f thay vì %f trong chuỗi định dạng của mình. Sau này sử dụng một mặc định hợp lý cho các chữ số thập phân (6 trong trường hợp của bạn) nhưng trước đây rõ ràng nói rằng bạn muốn rất nhiều.

Để an toàn, sử dụng một đồng bằng để kiểm tra xem nó đủ gần, chẳng hạn như:

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

Rõ ràng, vùng châu thổ phụ thuộc hoàn toàn vào nhu cầu của bạn. Nếu bạn đang nói tiền, 10 -5 có thể rất nhiều. Nếu bạn là một nhà vật lý, có lẽ bạn nên chọn một giá trị nhỏ hơn.

Tất nhiên, nếu bạn là một nhà toán học, không có sự thiếu chính xác là đủ nhỏ :-)

0

Chỉ vì một số hiển thị là "0,000000 "không có nghĩa là bằng 0,0. Hiển thị số thập phân có độ chính xác thấp hơn số có thể lưu trữ.

Có thể thuật toán của bạn đạt đến điểm rất gần 0, nhưng bước tiếp theo sẽ di chuyển quá nhỏ đến mức nó giống như trước đây, và do đó nó không bao giờ tiến gần hơn đến 0 (chỉ đi vào một vòng lặp vô hạn).

Nói chung, bạn không nên so sánh các số dấu phẩy động với ==!=. Bạn nên luôn kiểm tra xem chúng có nằm trong một phạm vi nhỏ nhất định hay không (thường được gọi là epsilon). Ví dụ:

while(fabs(tmp) >= 0.0001) 

Sau đó, nó sẽ dừng lại khi nó được hợp lý gần tuyên bố 0.

0

Việc in ấn được hiển thị một giá trị làm tròn, nó không được in chính xác cao nhất có thể. Vì vậy, vòng lặp của bạn chưa thực sự đạt đến số không.

(Và, như những người khác đã đề cập, do các vấn đề làm tròn nó có thể thực sự không bao giờ đạt được nó. So sánh giá trị so với một giới hạn nhỏ là do đó mạnh mẽ hơn so với bình đẳng với 0.0.)

0

Rất nhiều cuộc thảo luận về nguyên nhân, nhưng đây là giải pháp thay thế:

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; 
} 

Cách tiếp cận này tập trung vào việc mỗi giá trị giảm của tmp có tạo ra sự khác biệt hữu hình cho tổng không. Nó dễ dàng hơn so với làm việc ra một số ngưỡng từ 0 mà tmp trở nên không đáng kể, và có thể chấm dứt sớm hơn mà không thay đổi kết quả.

Lưu ý rằng khi bạn tính tổng số tương đối lớn với số tương đối nhỏ, các chữ số có nghĩa trong kết quả giới hạn độ chính xác. Bằng cách ngược lại, nếu bạn tính một số lượng nhỏ, sau đó thêm nó vào cái lớn, thì bạn có thể có đủ để làm to cái lớn lên một chút. Trong thuật toán của bạn giá trị tmp nhỏ không được tổng hợp với nhau anyway, do đó, không có tích lũy trừ khi mỗi thực sự ảnh hưởng đến tổng hợp - do đó cách tiếp cận ở trên hoạt động mà không ảnh hưởng đến độ chính xác hơn nữa.