So sánh tương đối các số dấu phẩy động


10

Tôi có một hàm số f(x, y)trả về một số dấu phẩy động kép thực hiện một số công thức và tôi muốn kiểm tra xem nó có đúng với các biểu thức phân tích cho tất cả sự kết hợp của các tham số xytôi quan tâm không. Cách nào là đúng để so sánh tính toán và phân tích số dấu phẩy động?

Hãy nói hai số là ab. Cho đến nay tôi đã chắc chắn rằng cả lỗi tuyệt đối ( abs(a-b) < eps) cũng như tương đối ( abs(a-b)/max(abs(a), abs(b)) < eps) đều nhỏ hơn eps. Bằng cách đó, nó sẽ bắt được những điểm không chính xác về số ngay cả khi các con số được cho là khoảng 1e-20.

Tuy nhiên, hôm nay tôi phát hiện ra một vấn đề, giá trị số avà giá trị phân tích blà:

In [47]: a                                                                     
Out[47]: 5.9781943146790832e-322

In [48]: b                                                                     
Out[48]: 6.0276008792632078e-322

In [50]: abs(a-b)                                                              
Out[50]: 4.9406564584124654e-324

In [52]: abs(a-b) / max(a, b)                                                  
Out[52]: 0.0081967213114754103

Vì vậy, lỗi tuyệt đối [50] là (rõ ràng) nhỏ, nhưng lỗi tương đối [52] là lớn. Vì vậy, tôi nghĩ rằng tôi có một lỗi trong chương trình của tôi. Bằng cách gỡ rối, tôi nhận ra, đó là những con số này denormal . Như vậy, tôi đã viết các thói quen sau đây để làm so sánh tương đối thích hợp:

real(dp) elemental function rel_error(a, b) result(r)
real(dp), intent(in) :: a, b
real(dp) :: m, d
d = abs(a-b)
m = max(abs(a), abs(b))
if (d < tiny(1._dp)) then
    r = 0
else
    r = d / m
end if
end function

Trường hợp tiny(1._dp)trả về 2.22507385850720138E-308 trên máy tính của tôi. Bây giờ mọi thứ hoạt động và tôi chỉ đơn giản nhận được 0 là lỗi tương đối và tất cả đều ổn. Cụ thể, lỗi tương đối ở trên [52] là sai, nó đơn giản là do không đủ độ chính xác của các số bất thường. Là tôi thực hiện các rel_errorchức năng chính xác? Tôi có nên kiểm tra nó abs(a-b)nhỏ hơn nhỏ (= không bình thường) và trả về 0 không? Hoặc tôi nên kiểm tra một số kết hợp khác, như thế max(abs(a), abs(b))nào?

Tôi chỉ muốn biết cách "thích hợp" là gì.

Câu trả lời:


11

Bạn có thể trực tiếp kiểm tra các biến thể bằng cách sử dụng isnormal()từ math.h(C99 trở lên, POSIX.1 trở lên). Trong Fortran, nếu mô-đun ieee_arithmeticcó sẵn, bạn có thể sử dụng ieee_is_normal(). Để chính xác hơn về sự bình đẳng mờ, bạn phải xem xét biểu diễn dấu phẩy động của các biến thể và quyết định ý nghĩa của bạn đối với kết quả là đủ tốt.

Hơn nữa, để tin rằng một trong hai kết quả là chính xác, bạn phải chắc chắn rằng bạn đã không mất quá nhiều chữ số ở bước trung gian. Việc tính toán với các biến thể nói chung là không đáng tin cậy và nên tránh bằng cách giải thuật lại nội bộ của bạn. Để đảm bảo quy mô nội bộ của bạn thành công, tôi khuyên bạn nên kích hoạt các ngoại lệ dấu phẩy động bằng feenableexcept()C99 hoặc ieee_arithmeticmô-đun trong Fortran.

Mặc dù bạn có thể yêu cầu ứng dụng của mình bắt được tín hiệu phát sinh trên các ngoại lệ dấu phẩy động, tất cả các hạt nhân tôi đã thử đặt lại cờ phần cứng để fetestexcept()không trả về kết quả hữu ích. Khi chạy với -fp_trap, các chương trình PETSc sẽ (theo mặc định) sẽ in dấu vết ngăn xếp khi có lỗi dấu phẩy động, nhưng sẽ không xác định được dòng vi phạm. Nếu bạn chạy trong trình gỡ lỗi, trình gỡ lỗi sẽ giữ cờ phần cứng và phá vỡ biểu thức vi phạm. Bạn có thể kiểm tra lý do chính xác bằng cách gọi fetestexcepttừ trình gỡ lỗi trong đó kết quả là bit HOẶC của các cờ sau (giá trị có thể thay đổi theo máy, xem fenv.h; các giá trị này dành cho x86-64 với glibc).

  • FE_INVALID = 0x1
  • FE_DIVBYZERO = 0x4
  • FE_OVERFLOW = 0x8
  • FE_UNDERFLOW = 0x10
  • FE_INEXACT = 0x20

Cảm ơn câu trả lời tuyệt vời. Biểu thức phân tích mà tôi so sánh với chế độ tiệm cận là exp(log_gamma(m+0.5_dp) - (m+0.5_dp)*log(t)) / 2cho m = 234, t = 2000. Nó đi về không nhanh chóng khi tôi tăng lên m. Tất cả tôi muốn đảm bảo rằng thói quen số của tôi trả về các số "chính xác" (để trả về số 0 cũng hoàn toàn tốt) với ít nhất 12 chữ số có nghĩa. Vì vậy, nếu phép tính trả về một số bất thường, thì nó chỉ đơn giản là 0 và sẽ không có vấn đề gì. Vì vậy, chỉ cần các thói quen so sánh cần phải mạnh mẽ để chống lại điều này.
Ondřej Čertík

5

Donald Knuth có một đề xuất về thuật toán so sánh dấu phẩy động trong tập 2 "Thuật toán tổng hợp" của "Nghệ thuật lập trình máy tính". Nó được thực hiện trong C bởi Th. Belding (xem gói fcmp ) và có sẵn trong GSL .


2
Đây là triển khai Fortran của tôi: gist.github.com/3776847 , lưu ý rằng tôi cần xử lý rõ ràng các số bất thường trong bất kỳ cách nào trong đó. Mặt khác, tôi nghĩ rằng nó khá giống với lỗi tương đối, sự khác biệt duy nhất là thay vì làm abs(a-b)/max(a, b) < eps, chúng tôi làm abs(a-b)/2**exponent(max(a, b)) < eps, điều này khá nhiều chỉ làm rơi lớp phủ trong max(a, b), vì vậy theo tôi, sự khác biệt là không đáng kể.
Ondřej Čertík

5

Các số không chuẩn hóa được làm tròn tối ưu thực sự có thể có sai số tương đối cao. (Việc xóa nó thành 0 trong khi vẫn gọi đó là lỗi tương đối là sai lệch.)

Nhưng gần bằng không, tính toán tương đối là vô nghĩa.

Do đó, ngay cả trước khi bạn đạt được các số không chuẩn hóa, có lẽ bạn nên chuyển sang độ chính xác tuyệt đối (cụ thể là số bạn muốn đảm bảo trong trường hợp này).

yx|yx|absacc+relaccmax(|x|,|y|)

Sau đó, người dùng mã của bạn biết chính xác họ thực sự có bao nhiêu độ chính xác.


Bạn có chắc chắn rằng việc tính các lỗi tương đối gần bằng 0 là vô nghĩa? Tôi nghĩ rằng nó chỉ vô nghĩa nếu có sự mất chính xác (vì bất kỳ lý do gì). Ví dụ, nếu mất độ chính xác cho x <1e-150 do một số vấn đề về số (như trừ hai số lớn), thì bạn đã đúng. Tuy nhiên, trong trường hợp của tôi, các con số dường như chính xác đến tận không, ngoại trừ khi nó chạm các số không bình thường. Vì vậy, trong trường hợp của tôi absacc = 1e-320 hoặc hơn và tôi chỉ có thể kiểm tra abs(a-b) < tiny(1._dp)như tôi làm ở trên.
Ondřej Čertík

@ OndřejČertík: Trong trường hợp đó, thay thế 1e-150 bằng 1e-300 hoặc bất kỳ ràng buộc nào bạn có thể xác minh. Trong mọi trường hợp rất gần với 0, bạn tạo ra một lỗi tuyệt đối và yêu cầu lỗi của bạn sẽ phản ánh điều này thay vì tuyên bố lỗi tương đối bằng không.
Arnold Neumaier

Tôi hiểu rồi. Tôi có thể xác minh rằng tất cả các công trình cho các số cao hơn tiny(1._dp)=2.22507385850720138E-308(tôi đã nhầm lẫn trong nhận xét trước đây của mình, đó là 2e-308, không phải 1e-320). Vì vậy, đây là lỗi tuyệt đối của tôi. Sau đó, tôi cần phải so sánh các lỗi tương đối. Tôi thấy quan điểm của bạn, tôi nghĩ bạn đúng. Cảm ơn!
Ondřej Čertík

1
|yx|absaccmax(|x|,|y|)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.