Phân tích lỗi số trong hàm C ++


20

Giả sử rằng tôi có một hàm lấy đầu vào một số giá trị dấu phẩy động (đơn hoặc kép), thực hiện một số tính toán và tạo ra các giá trị dấu phẩy động đầu ra (cũng là một hoặc hai). Tôi đang làm việc chủ yếu với MSVC 2008, nhưng cũng có kế hoạch làm việc với MinGW / GCC. Tôi đang lập trình trong C ++.

Cách điển hình để đo lường lập trình có bao nhiêu lỗi trong kết quả? Giả sử rằng tôi cần sử dụng một thư viện chính xác tùy ý: thư viện nào tốt nhất như vậy nếu tôi không quan tâm đến tốc độ?

Câu trả lời:


17

Nếu bạn đang tìm kiếm một ràng buộc tốt cho lỗi làm tròn của mình, bạn không nhất thiết cần một thư viện chính xác. Bạn có thể sử dụng phân tích lỗi chạy thay thế.

Tôi không thể tìm thấy một tài liệu tham khảo trực tuyến tốt, nhưng tất cả được mô tả trong Phần 3.3 của cuốn sách "Độ chính xác và ổn định của thuật toán số" của Nick Higham. Ý tưởng khá đơn giản:

  1. Xác định lại mã của bạn để bạn có một phép gán duy nhất cho một phép toán số học trên mỗi dòng.
  2. Đối với mỗi biến, ví dụ: xtạo một biến x_errđược khởi tạo về 0 khi xđược gán một hằng số.
  3. Đối với mỗi hoạt động, ví dụ z = x * y, cập nhật biến z_errbằng mô hình chuẩn của số học dấu phẩy động và kết quả zcũng như các lỗi đang chạy x_erry_err.
  4. Giá trị trả về của hàm của bạn sau đó cũng sẽ có một _errgiá trị tương ứng được đính kèm với nó. Đây là một phụ thuộc dữ liệu bị ràng buộc vào tổng số lỗi vòng của bạn.

Phần khó khăn là bước 3. Đối với các phép toán số học đơn giản nhất, bạn có thể sử dụng các quy tắc sau:

  • z = x + y -> z_err = u*abs(z) + x_err + y_err
  • z = x - y -> z_err = u*abs(z) + x_err + y_err
  • z = x * y -> z_err = u*abs(z) + x_err*abs(y) + y_err*abs(x)
  • z = x / y -> z_err = u*abs(z) + (x_err*abs(y) + y_err*abs(x))/y^2
  • z = sqrt(x) -> z_err = u*abs(z) + x_err/(2*abs(z))

nơi u = eps/2là đơn vị roundoff. Vâng, các quy tắc cho +-là như nhau. Các quy tắc cho bất kỳ hoạt động nào khác op(x)có thể được trích xuất dễ dàng bằng cách sử dụng mở rộng chuỗi Taylor của kết quả được áp dụng cho op(x + x_err). Hoặc bạn có thể thử googling. Hoặc sử dụng sách của Nick Higham.

Ví dụ, hãy xem xét mã Matlab / Octave sau đây để đánh giá một đa thức trong các hệ số atại một điểm xbằng cách sử dụng sơ đồ Horner:

function s = horner ( a , x )
    s = a(end);
    for k=length(a)-1:-1:1
        s = a(k) + x*s;
    end

Bước đầu tiên, chúng tôi chia hai hoạt động trong s = a(k) + x*s:

function s = horner ( a , x )
    s = a(end);
    for k=length(a)-1:-1:1
        z = x*s;
        s = a(k) + z;
    end

Sau đó chúng tôi giới thiệu các _errbiến. Lưu ý rằng các đầu vào axđược giả định là chính xác, nhưng chúng tôi cũng có thể yêu cầu người dùng chuyển các giá trị tương ứng cho a_errx_err:

function [ s , s_err ] = horner ( a , x )
    s = a(end);
    s_err = 0;
    for k=length(a)-1:-1:1
        z = x*s;
        z_err = ...;
        s = a(k) + z;
        s_err = ...;
    end

Cuối cùng, chúng tôi áp dụng các quy tắc được mô tả ở trên để có được các điều khoản lỗi:

function [ s , s_err ] = horner ( a , x )
    u = eps/2;
    s = a(end);
    s_err = 0;
    for k=length(a)-1:-1:1
        z = x*s;
        z_err = u*abs(z) + s_err*abs(x);
        s = a(k) + z;
        s_err = u*abs(s) + z_err;
    end

Lưu ý rằng vì chúng tôi không có a_errhoặc x_err, ví dụ như chúng được coi là 0, các thuật ngữ tương ứng chỉ đơn giản được bỏ qua trong các biểu thức lỗi.

Et voilà! Bây giờ chúng ta có sơ đồ Horner trả về ước tính lỗi phụ thuộc dữ liệu (lưu ý: đây là giới hạn trên của lỗi) cùng với kết quả.

Một lưu ý phụ, vì bạn đang sử dụng C ++, bạn có thể xem xét tạo lớp của riêng mình cho các giá trị dấu phẩy động mang theo _errthuật ngữ và làm quá tải tất cả các phép toán số học để cập nhật các giá trị này như được mô tả ở trên. Đối với các mã lớn, đây có thể là tuyến dễ dàng hơn, mặc dù tính toán kém hiệu quả hơn. Có nói rằng, bạn có thể tìm thấy một lớp học trực tuyến như vậy. Một tìm kiếm nhanh trên Google đã cho tôi liên kết này .

±bạnx(1±bạn)


1
+1 cho phân tích này, vì nó thú vị. Tôi thích công việc của Higham. Điều tôi quan tâm là việc yêu cầu người dùng viết mã bổ sung đó bằng tay (thay vì bán tự động như số học khoảng) có thể dễ bị lỗi vì số lượng các phép toán số trở nên lớn.
Geoff Oxberry

1
@GeoffOxberry: Tôi hoàn toàn đồng ý với vấn đề phức tạp. Đối với các mã lớn hơn, tôi thực sự khuyên bạn nên viết một lớp / kiểu dữ liệu làm quá tải các hoạt động tăng gấp đôi như chỉ phải thực hiện chính xác từng thao tác một lần. Tôi khá ngạc nhiên khi dường như không có thứ gì như thế này cho Matlab / Octave.
Pedro

Tôi thích phân tích này, nhưng vì việc tính toán các thuật ngữ lỗi cũng được thực hiện theo dấu phẩy động, nên các thuật ngữ lỗi đó không chính xác do lỗi dấu phẩy động?
plasmacel

8

Một thư viện mã nguồn mở và di động đẹp cho số học dấu phẩy động chính xác tùy ý (và nhiều thứ khác) là NTL của Victor Shoup , có sẵn ở dạng nguồn C ++.

Ở cấp độ thấp hơn là Thư viện Bignum GNU Nhiều độ chính xác (GMP) , cũng là một gói nguồn mở.

NTL có thể được sử dụng với GMP là cần có hiệu suất nhanh hơn, nhưng NTL cung cấp các thói quen cơ bản riêng mà chắc chắn có thể sử dụng được nếu bạn "không quan tâm đến tốc độ". GMP tuyên bố là "thư viện bignum nhanh nhất". GMP phần lớn được viết bằng C, nhưng có giao diện C ++.

Đã thêm: Mặc dù số học khoảng có thể đưa ra giới hạn trên và dưới cho câu trả lời chính xác theo cách tự động, nhưng điều này không đo chính xác lỗi trong tính toán chính xác "tiêu chuẩn" vì kích thước khoảng thường tăng theo từng thao tác (theo tương đối hoặc cảm giác lỗi tuyệt đối).

Cách điển hình để tìm kích thước lỗi, cho các lỗi làm tròn hoặc cho các lỗi về sự rời rạc, v.v., là tính toán một giá trị chính xác bổ sung và so sánh với giá trị chính xác "tiêu chuẩn". Chỉ cần độ chính xác bổ sung khiêm tốn để xác định kích thước lỗi chính xác đến độ chính xác hợp lý, vì chỉ riêng các lỗi làm tròn là lớn hơn đáng kể về độ chính xác "tiêu chuẩn" so với độ chính xác phụ.

Điểm có thể được minh họa bằng cách so sánh các tính toán chính xác đơn và kép. Lưu ý rằng trong C ++, các biểu thức trung gian luôn được tính toán với độ chính xác gấp đôi (ít nhất), vì vậy nếu chúng ta muốn minh họa tính toán trong độ chính xác đơn "thuần túy" sẽ như thế nào, chúng ta cần lưu trữ các giá trị trung gian ở độ chính xác đơn.

Đoạn mã C

    float fa,fb;
    double da,db,err;
    fa = 4.0;
    fb = 3.0;
    fa = fa/fb;
    fa -= 1.0;

    da = 4.0;
    db = 3.0;
    da = da/db;
    da -= 1.0;

    err = fa - da;
    printf("Single precision error wrt double precision value\n");
    printf("Error in getting 1/3rd is %e\n",err);
    return 0;

Đầu ra từ phía trên (chuỗi công cụ Cygwin / MinGW32 GCC):

Single precision error wrt double precision value
Error in getting 1/3rd is 3.973643e-08

Do đó, lỗi là về những gì người ta mong đợi khi làm tròn 1/3 đến độ chính xác đơn. Người ta sẽ không (tôi nghi ngờ) quan tâm đến việc nhận được nhiều hơn một vài vị trí thập phân trong lỗi chính xác, vì việc đo lỗi là cho độ lớn và không chính xác.


Cách tiếp cận của bạn chắc chắn là âm thanh toán học. Tôi nghĩ rằng sự đánh đổi là nghiêm ngặt; những người có lỗi về phạm vi sẽ chỉ ra sự nghiêm ngặt của số học khoảng, nhưng tôi nghi ngờ rằng trong nhiều ứng dụng, tính toán với độ chính xác cao hơn là đủ, và các ước tính lỗi kết quả có thể sẽ chặt chẽ hơn, như bạn chỉ ra.
Geoff Oxberry

Đây là cách tiếp cận tôi đang tưởng tượng rằng tôi sẽ sử dụng. Tôi có thể thử một vài trong số các kỹ thuật khác nhau này để xem cái nào phù hợp nhất cho ứng dụng của tôi. Cập nhật ví dụ mã được nhiều đánh giá cao!
user_123abc

7

GMP (tức là thư viện GNU Multi Precision) là thư viện chính xác tùy ý tốt nhất mà tôi biết.

Tôi không biết bất kỳ cách lập trình nào để đo lỗi trong kết quả của hàm dấu phẩy động tùy ý. Một điều bạn có thể thử là tính toán một phần mở rộng khoảng của hàm bằng số học khoảng . Trong C ++, bạn sẽ phải sử dụng một số loại thư viện để tính toán các phần mở rộng khoảng; một thư viện như vậy là Thư viện Số học Khoảng thời gian Boost. Về cơ bản, để đo lỗi, bạn sẽ cung cấp dưới dạng đối số cho các khoảng chức năng của mình có độ rộng gấp 2 lần đơn vị (khoảng), tập trung vào các giá trị quan tâm, và sau đó đầu ra của bạn sẽ là một tập hợp các khoảng, độ rộng của mà sẽ cung cấp cho bạn một số ước tính bảo thủ của lỗi. Một khó khăn với cách tiếp cận này là số học khoảng được sử dụng trong thời trang này có thể đánh giá quá cao sai số theo số lượng đáng kể, nhưng cách tiếp cận này là cách "lập trình" nhất mà tôi có thể nghĩ ra.


Ah, tôi chỉ nhận thấy số học khoảng được đề cập trong câu trả lời của bạn ... Được nâng cấp!
Ali

2
Richard Harris đã viết một loạt bài viết xuất sắc trên tạp chí ACCU Overload về Floating Point Blues . Bài viết của ông về số học khoảng là trong Quá tải 103 ( pdf , p19-24).
Đánh dấu gian hàng

6

Ước tính lỗi nghiêm ngặt và tự động có thể đạt được bằng cách phân tích khoảng . Bạn làm việc với các khoảng thay vì số. Ví dụ:

[a,b] + [c,d] = [min(a+c, a+d, b+c, b+d), max (a+c, a+d, b+c, b+d)] = [a+c, b+d]

Làm tròn số cũng có thể được xử lý chặt chẽ, xem Số học khoảng thời gian làm tròn .

Miễn là đầu vào của bạn bao gồm các khoảng hẹp, các ước tính là OK và rẻ tiền để tính toán. Thật không may, lỗi thường được đánh giá quá cao, xem vấn đề phụ thuộc .

Tôi không biết bất kỳ thư viện số học khoảng thời gian chính xác tùy ý.

Nó phụ thuộc vào vấn đề của bạn trong tay liệu số học khoảng có thể phục vụ nhu cầu của bạn hay không.


4

Các thư viện GNU MPFR là một thư viện phao độc đoán chính xác có độ chính xác cao (đặc biệt là làm tròn chính xác cho tất cả các hoạt động, mà không phải là dễ dàng như nó âm thanh) là một trong những điểm trọng tâm chính của họ. Nó sử dụng GNU MP dưới mui xe. Nó có một phần mở rộng gọi là MPFI thực hiện phép tính số học, mà - như câu trả lời của Geoff - có thể có ích cho mục đích xác minh: tiếp tục tăng độ chính xác làm việc cho đến khi khoảng kết quả rơi vào giới hạn nhỏ.

Điều này sẽ không luôn luôn làm việc, mặc dù; đặc biệt là nó không nhất thiết hiệu quả nếu bạn đang làm một cái gì đó như tích hợp số, trong đó mỗi bước mang một "lỗi" độc lập với các vấn đề làm tròn. Trong trường hợp đó, hãy thử một gói chuyên dụng như vô cực COZY , điều này thực hiện rất tốt việc sử dụng các thuật toán cụ thể để ràng buộc lỗi tích hợp (và sử dụng cái gọi là mô hình Taylor thay vì các khoảng).


Tôi đồng ý; tích hợp số chắc chắn là một trường hợp trong đó số học khoảng thời gian ngây thơ có thể đi sai. Tuy nhiên, ngay cả các mô hình Taylor sử dụng số học khoảng. Tôi quen thuộc với công việc của Makino và Berz và tôi tin rằng họ sử dụng mô hình Taylor theo nghĩa của RE Moore, mặc dù họ cũng sử dụng các thủ thuật liên quan đến cái mà họ gọi là "đại số vi phân".
Geoff Oxberry

@GeoffOxberry: Vâng - Tôi nghĩ đại số vi phân này là công cụ để có một ràng buộc về lỗi trong bước tích hợp.
Erik P.

0

Tôi được bảo rằng MPIR là một thư viện tốt để sử dụng nếu bạn đang làm việc với Visual Studio:

http://mpir.org/


Chào mừng bạn đến với SciComp.SE! Bạn có thể thêm một số chi tiết về cách thư viện này có thể được sử dụng để đo lỗi của các phép tính dấu phẩy động không?
Christian Clason

Tôi sẽ thử; Tôi thực sự chưa thiết lập MPIR trên máy tính của tôi! Tôi đã thiết lập GMP và MPFR.
fishermanhat
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.