2009-03-30 15 views
6

Tôi có một getSlope chức năng mà mất như thông số 4 đôi và trả về một đôi tính toán sử dụng này thông số được đưa ra trong các cách sau:mất bất ngờ độ chính xác khi chia đôi

double QSweep::getSlope(double a, double b, double c, double d){ 
double slope; 
slope=(d-b)/(c-a); 
return slope; 
} 

Vấn đề là khi gọi chức năng này với đối số ví dụ:

getSlope(2.71156, -1.64161, 2.70413, -1.72219); 

kết quả trả về là:

10.8557 

và đây không phải là kết quả tốt cho tính toán của tôi. tôi đã tính toán độ dốc sử dụng Mathematica và kết quả cho độ dốc cho các thông số tương tự là:

10.8452 

hoặc với nhiều chữ số cho độ chính xác:

10.845222072678331. 

Kết quả trả về bởi chương trình của tôi là không tốt trong tính toán tiếp theo của tôi. Hơn nữa, tôi không chắc chắn cách chương trình trả về 10.8557 bắt đầu từ 10.845222072678331 (giả sử rằng đây là kết quả gần đúng cho việc phân chia)? Làm cách nào để có kết quả tốt cho sự phân chia của tôi?

cảm ơn bạn trước, madalina


tôi in kết quả bằng cách sử dụng dòng lệnh:

std::cout<<slope<<endl; 

Nó có thể là thông số của tôi là có lẽ không tốt, như tôi đọc chúng từ một chương trình khác (mà tính toán đồ thị, sau khi tôi đọc các thông số này từ đồ thị của mình, tôi chỉ hiển thị chúng để xem giá trị của chúng, nhưng có lẽ các vectơ được hiển thị không có độ chính xác nội tại tương đương với giá trị được tính .. Tôi không biết nó thực sự lạ Một số al lỗi xuất hiện ..)

Khi biểu đồ mà từ đó tôi đọc thông số của tôi được tính toán, một số thư viện số được viết bằng C++ (có mẫu) được sử dụng. Không có OpenGL được sử dụng cho tính toán này.

cảm ơn bạn, madalina

+0

Kiểm tra cách phương thức được biên dịch trong asm. Bạn có thể làm điều đó trong trình gỡ rối tôi tin (ít nhất là trong studio trực quan). –

+0

Cửa sổ của tôi calc cho kết quả tốt như Mathematica: D – klew

+0

Trình biên dịch nào, nền tảng nào? – peterchen

Trả lời

5

Nó có thể được rằng bạn sử dụng DirectX hoặc OpenGL trong dự án của bạn? Nếu vậy họ có thể tắt chính xác gấp đôi và bạn sẽ nhận được kết quả lạ.

Bạn có thể kiểm tra các thiết lập chính xác của bạn với

std::sqrt(x) * std::sqrt(x) 

Kết quả có được khá gần với x. Tôi đã gặp vấn đề này từ lâu và dành một tháng để kiểm tra tất cả các công thức. Nhưng sau đó tôi đã tìm thấy

D3DCREATE_FPU_PRESERVE 
+0

chính xác làm thế nào để họ đi về làm điều đó? –

+0

Có các tùy chọn khi khởi tạo trực tiếp 3d. Tôi không nhớ tên nhưng tôi mất một tháng kiểm tra tất cả các công thức Diploma và chỉ sau đó tôi thực hiện kiểm tra đơn giản với "sqrt (x) * sqrt (x)" và độ chính xác bị hỏng rất nhiều trừ khi tôi tắt tùy chọn. –

+0

Khi được biên dịch với ứng dụng giao diện điều khiển Win32 tiêu chuẩn trong VS2008, nó cung cấp câu trả lời đúng. Tôi đồng ý và nói đó là một thiết lập trình biên dịch. –

3

Vấn đề ở đây là (c-a) nhỏ, do đó các lỗi làm tròn có trong hoạt động điểm nổi được phóng đại trong ví dụ này. Một giải pháp chung là để làm lại phương trình của bạn để bạn không chia cho một số nhỏ, tôi không chắc chắn làm thế nào bạn sẽ làm điều đó ở đây mặc dù.

EDIT:

Neil là ngay trong bình luận của ông cho câu hỏi này, tôi tính toán câu trả lời trong VB sử dụng giường đôi và nhận được câu trả lời tương tự như Mathematica.

+0

xem mã tôi đã đăng - đó không phải là vấn đề –

5

Các mã sau đây:

#include <iostream> 
using namespace std; 

double getSlope(double a, double b, double c, double d){ 
    double slope; 
    slope=(d-b)/(c-a); 
    return slope; 
} 

int main() { 
    double s = getSlope(2.71156, -1.64161, 2.70413, -1.72219); 
    cout << s << endl; 
} 

đưa ra một kết quả của 10,8452 với g ++. Làm thế nào bạn in ra kết quả trong mã của bạn?

+0

Việc bạn in 10 là không quan trọng.845222072678331, nó sẽ không 'tròn' hoặc cắt ngắn thành 10.8557 –

1

Tốt hơn In ra các đối số. Khi bạn đang có, như tôi đoán, chuyển các tham số trong ký hiệu thập phân, bạn sẽ mất độ chính xác cho mỗi và tất cả chúng. Vấn đề là 1/5 là một chuỗi vô tận trong hệ nhị phân, ví dụ: 0,2 trở thành .001001001 .... Ngoài ra, số thập phân được cắt nhỏ khi chuyển đổi một phao nhị phân thành một biểu diễn văn bản theo dạng thập phân.

Bên cạnh đó, đôi khi trình biên dịch chọn tốc độ chính xác hơn. Điều này sẽ là một chuyển đổi trình biên dịch được ghi lại.

0

Patrick có vẻ là đúng về (ca) là nguyên nhân chính:

db = -1,72219 - (-1,64161) = -0,08058

ca = 2, 70.413 - 2,71156 = -0,00743

S = (db)/(ca) = -0,08058/-0,00743 = 10,845222

Bạn bắt đầu với sáu chữ số chính xác , thông qua phép trừ bạn nhận được giảm xuống còn 3 và 4 chữ số. Đoán tốt nhất của tôi là bạn mất độ chính xác phụ vì số lượng -0,00743 không thể được đại diện exaclty trong một đôi. Hãy thử sử dụng các biến trung gian có độ chính xác lớn hơn, như sau:

double QSweep::getSlope(double a, double b, double c, double d) 
{ 
    double slope; 
    long double temp1, temp2; 

    temp1 = (d-b); 
    temp2 = (c-a); 
    slope = temp1/temp2; 

    return slope; 
} 
+0

bạn có nhìn vào mã tôi đã đăng không? –

+0

Bạn có vẻ đã nhầm lẫn chính xác (cách số được biểu diễn) với độ chính xác (những gì dung sai trên các giá trị). Cho dù bạn chỉ định số double là 2,70413 hoặc 2,7041300000 thì không có sự khác biệt nào dẫn đến C++ –

+0

@Pete Kirkham: Không thể biểu thị giá trị của ví dụ: 0,1 * chính xác * trong một đôi, do đó, lưu trữ nó trong một biến với một phạm vi lớn hơn có thể cho kết quả khác nhau. – Treb

7

Tôi đã thử với phao thay vì gấp đôi và tôi nhận được 10.845110 kết quả. Nó vẫn có vẻ tốt hơn so với kết quả madalina.

EDIT:

Tôi nghĩ rằng tôi biết lý do bạn nhận được kết quả này. Nếu bạn nhận được một tham số a, b, c và d từ một nơi khác và bạn in nó, nó sẽ cho bạn giá trị làm tròn. Sau đó, nếu bạn đặt nó vào Mathemtacia (hoặc calc;)) nó sẽ cho bạn kết quả khác nhau.

Tôi đã thử thay đổi một chút thông số của bạn. Khi tôi đã làm:

double c = 2.7041304; 

Tôi nhận được 10.845806. Tôi chỉ thêm 0.0000004 vào c! Vì vậy, tôi nghĩ rằng "lỗi" của bạn không phải là lỗi. In a, b, c và d với độ chính xác cao hơn và sau đó đặt chúng vào Mathematica.

2

Kết quả bạn nhận được nhất quán với số học 32 bit. Nếu không biết nhiều hơn về môi trường của bạn, bạn không thể khuyên nên làm gì.

Giả sử mã được hiển thị là những gì đang chạy, nghĩa là bạn không chuyển đổi bất kỳ thứ gì thành chuỗi hoặc phao, thì không có sự sửa lỗi trong C++. Nó nằm ngoài mã bạn đã hiển thị và phụ thuộc vào môi trường.

Vì Patrick McDonald và Treb đã đưa cả độ chính xác của các yếu tố đầu vào của bạn và lỗi trên a-c, tôi nghĩ tôi sẽ xem xét điều đó. Một kỹ thuật để xem xét các lỗi làm tròn là số học khoảng, mà làm cho giới hạn trên và dưới mà giá trị đại diện cho rõ ràng (chúng được ngầm định trong số dấu chấm động, và được cố định với độ chính xác của biểu diễn). Bằng cách xử lý mỗi giá trị dưới dạng giới hạn trên và dưới, và bằng cách mở rộng giới hạn bởi lỗi trong biểu diễn (xấp xỉ x * 2^-53 với giá trị gấp đôi x), bạn sẽ nhận được kết quả cho các giới hạn dưới và phía trên tính chính xác của một giá trị, có tính đến lỗi chính xác trường hợp xấu nhất. Ví dụ: nếu bạn có giá trị trong khoảng [1.0, 2.0] và trừ giá trị đó trong phạm vi [0.0, 1.0], thì kết quả phải nằm trong phạm vi [bên dưới (0.0), ở trên (2.0)] là kết quả tối thiểu là 1.0-1.0 và tối đa là 2.0-0.0. belowabove tương đương với sàn và trần, nhưng đối với giá trị biểu thị tiếp theo thay vì cho số nguyên.

Sử dụng khoảng thời gian đó đại diện cho trường hợp xấu nhất làm tròn kép:

getSlope(
a = [2.7115599999999995262:2.7115600000000004144], 
b = [-1.6416099999999997916:-1.6416100000000002357], 
c = [2.7041299999999997006:2.7041300000000005888], 
d = [-1.7221899999999998876:-1.7221900000000003317]) 
(d-b) = [-0.080580000000000526206:-0.080579999999999665783] 
(c-a) = [-0.0074300000000007129439:-0.0074299999999989383218] 

to double precision [10.845222072677243474:10.845222072679954195] 

Vì vậy, mặc dù c-a là nhỏ so với c hoặc a, nó vẫn còn lớn so với làm tròn đôi, vì vậy nếu bạn đang sử dụng đôi tồi tệ nhất có thể tưởng tượng làm tròn chính xác, sau đó bạn có thể tin tưởng rằng giá trị của chính xác là 12 con số - 10.8452220727. Bạn đã mất một vài con số giảm độ chính xác gấp đôi, nhưng bạn vẫn đang làm việc nhiều hơn ý nghĩa đầu vào của bạn.

Nhưng nếu đầu vào chỉ chính xác với số liệu quan trọng, thì thay vì là giá trị gấp đôi 2.71156 +/- eps, thì phạm vi đầu vào sẽ là [2,711555,2,711565], do đó bạn sẽ nhận được kết quả:

getSlope(
a = [2.711555:2.711565], 
b = [-1.641615:-1.641605], 
c = [2.704125:2.704135], 
d = [-1.722195:-1.722185]) 
(d-b) = [-0.08059:-0.08057] 
(c-a) = [-0.00744:-0.00742] 

to specified accuracy [10.82930108:10.86118598] 

là phạm vi rộng hơn nhiều. Tuy nhiên, bạn sẽ phải đi theo cách của bạn để theo dõi tính chính xác trong các phép tính, và các lỗi làm tròn trong dấu phẩy động là không đáng kể trong ví dụ này - nó chính xác đến 12 con số với trường hợp làm tròn chính xác gấp đôi trường hợp xấu nhất.

Mặt khác, nếu đầu vào của bạn chỉ được biết đến 6 con số, nó không thực sự quan trọng cho dù bạn nhận được 10.8557 hoặc 10.8452. Cả hai đều nằm trong [10.82930108: 10.86118598].

-1

Trong khi thảo luận học thuật diễn ra thật tuyệt vời cho việc tìm hiểu về những hạn chế của ngôn ngữ lập trình, bạn có thể tìm thấy giải pháp đơn giản nhất cho vấn đề là cấu trúc dữ liệu cho arbitrary precision arithmetic.

Điều này sẽ có một số chi phí, nhưng bạn sẽ có thể tìm thấy thứ gì đó có độ chính xác khá bảo đảm.

+1

Đề xuất số học chính xác tùy ý, mặc dù phổ biến trên StackOverflow, không phải là câu trả lời hay nhất cho mọi câu hỏi về tính toán dấu phẩy động. –

+0

Điều đó đúng, nhưng thường là giải pháp khả thi đơn giản nhất. Thường có những cách tốt hơn, nhanh hơn và phức tạp hơn để làm mọi thứ, nhưng ngay cả sự đơn giản cũng có rất nhiều giá trị trong một dự án phần mềm. – IanGilham