2011-10-27 24 views
8

Tôi đang phát triển một thư viện các chức năng toán học có mục đích đặc biệt trong C. Tôi cần cung cấp khả năng cho thư viện để xử lý cả độ chính xác đơn và độ chính xác gấp đôi. Điểm quan trọng ở đây là các hàm "đơn" nên sử dụng số học "đơn" duy nhất trong nội bộ (resp. Cho các hàm "double").Thiết kế đúng mã C xử lý cả điểm nổi chính xác đơn và đôi?

Như minh hoạ, hãy xem LAPACK (Fortran), cung cấp hai phiên bản của từng chức năng của nó (SINGLE và DOUBLE). Ngoài ra, thư viện toán học C (ví dụ: expfexp).

Để làm rõ, tôi muốn hỗ trợ một cái gì đó tương tự như sau (giả tạo) ví dụ:

float MyFloatFunc(float x) { 
    return expf(-2.0f * x)*logf(2.75f*x); 
} 

double MyDoubleFunc(double x) { 
    return exp(-2.0 * x)*log(2.75*x); 
} 

Tôi đã nghĩ về những phương pháp sau đây:

  1. Sử dụng macro cho tên hàm . Điều này vẫn yêu cầu hai mã nguồn riêng biệt:

    #ifdef USE_FLOAT 
    #define MYFUNC MyFloatFunc 
    #else 
    #define MYFUNC MyDoubleFunc 
    #endif 
    
  2. Sử dụng macro cho các loại dấu phẩy động. Điều này cho phép tôi để chia sẻ các codebase qua hai phiên bản khác nhau:

    #ifdef USE_FLOAT 
    #define NUMBER float 
    #else 
    #define NUMBER double 
    #endif 
    
  3. Chỉ cần phát triển hai thư viện riêng, và quên đi những cố gắng tiết kiệm nhức đầu.

Có ai có đề xuất hoặc đề xuất bổ sung không?

Trả lời

7

Đối với các phép tính gần đúng đa thức, nội suy và các hàm toán học xấp xỉ vốn khác, bạn không thể chia sẻ mã giữa độ chính xác kép và triển khai chính xác đơn mà không lãng phí thời gian trong phiên bản đơn chính xác hoặc xấp xỉ hơn mức cần thiết trong độ chính xác gấp đôi.

Tuy nhiên, nếu bạn đi theo con đường của codebase đơn, sau đây nên làm việc cho các hằng số và chức năng thư viện chuẩn:

#ifdef USE_FLOAT 
#define C(x) x##f 
#else 
#define C(x) x 
#endif 

... C(2.0) ... C(sin) ... 
+0

Vâng, cảm ơn vì điểm xuất sắc đó. Mục tiêu ở đây là để giảm tốc độ thực hiện của "đơn" so với lợi thế chính xác của "gấp đôi". –

2

Câu hỏi lớn cho bạn sẽ là:

  • Có dễ dàng hơn để duy trì hai cây nguồn không bị xáo trộn riêng biệt, hoặc một cây bị xáo trộn?

Nếu bạn có mã hóa phổ biến được đề xuất, bạn sẽ phải viết mã theo kiểu sàn, cẩn thận không viết bất kỳ hằng số chưa được đặt trước hoặc gọi hàm macro nào.

Nếu bạn có cây mã nguồn riêng biệt, mã sẽ đơn giản hơn để duy trì ở chỗ mỗi cây trông giống như mã C bình thường (không bị xáo trộn), nhưng nếu có lỗi trong YourFunctionA trong phiên bản 'float', bạn sẽ luôn luôn nhớ thực hiện thay đổi phù hợp trong phiên bản 'đôi'.

Tôi nghĩ điều này phụ thuộc vào độ phức tạp và biến động của các hàm. Nghi ngờ của tôi là một khi được viết và sửa lỗi lần đầu tiên, sẽ hiếm khi cần phải quay trở lại với nó. Điều này thực sự có nghĩa là không quan trọng bạn sử dụng cơ chế nào - cả hai đều có thể thực hiện được.Nếu các phần tử chức năng có phần dễ bay hơi, hoặc danh sách các hàm là dễ bay hơi, thì cơ sở mã đơn có thể dễ dàng hơn tổng thể. Nếu tất cả mọi thứ là rất ổn định, sự rõ ràng của hai cơ sở mã riêng biệt có thể làm cho thích hợp hơn. Nhưng nó rất chủ quan.

Tôi có thể đi với một mã cơ sở duy nhất và các macro trên tường. Nhưng tôi không chắc chắn đó là tốt nhất, và cách khác có lợi thế của nó quá.

5

(một phần lấy cảm hứng từ câu trả lời của Pascal Cuoq) Nếu bạn muốn một thư viện có phiên bản float và double của mọi thứ, bạn có thể sử dụng đệ quy #include s kết hợp với macro. Nó không dẫn đến việc rõ ràng nhất của mã, nhưng nó cho phép bạn sử dụng cùng mã cho cả hai phiên bản, và obfuscation là đủ mỏng nó có thể kiểm soát được:

mylib.h:

#ifndef MYLIB_H_GUARD 
    #ifdef MYLIB_H_PASS2 
    #define MYLIB_H_GUARD 1 
    #undef C 
    #undef FLT 
    #define C(X) X 
    #define FLT double 
    #else 
    /* any #include's needed in the header go here */ 

    #undef C 
    #undef FLT 
    #define C(X) X##f 
    #define FLT float 
    #endif 

    /* All the dual-version stuff goes here */ 
    FLT C(MyFunc)(FLT x); 

    #ifndef MYLIB_H_PASS2 
    /* prepare 2nd pass (for 'double' version) */ 
    #define MYLIB_H_PASS2 1 
    #include "mylib.h" 
    #endif 
#endif /* guard */ 

mylib.c:

#ifdef MYLIB_C_PASS2 
    #undef C 
    #undef FLT 
    #define C(X) X 
    #define FLT double 
#else 
    #include "mylib.h" 
    /* other #include's */ 

    #undef C 
    #undef FLT 
    #define C(X) X##f 
    #define FLT float 
#endif 

/* All the dual-version stuff goes here */ 
FLT C(MyFunc)(FLT x) 
{ 
    return C(exp)(C(-2.0) * x) * C(log)(C(2.75) * x); 
} 

#ifndef MYLIB_C_PASS2 
    /* prepare 2nd pass (for 'double' version) */ 
    #define MYLIB_C_PASS2 1 
    #include "mylib.c" 
#endif 

Mỗi tập tin #include s bản thân thêm một thời gian, sử dụng các định nghĩa vĩ mô khác nhau trên đèo thứ hai, để tạo ra hai phiên bản của mã sử dụng các macro.

Tuy nhiên, một số người có thể phản đối cách tiếp cận này.

+0

Tôi có thể sử dụng phương pháp này cho việc triển khai. Tôi sẽ không bao giờ sử dụng nó cho tiêu đề. Mọi người không cần phải xem loại chi tiết triển khai này khi họ chỉ muốn sử dụng thư viện. Tôi cũng sẽ di chuyển tất cả các định nghĩa macro vào một tệp bao gồm. –

+0

Rất đáng sợ - và rất thú vị. Không bao giờ biết rằng tệp tiêu đề có thể tự bao gồm #. Tôi tự hỏi nếu lừa này làm cho tiền xử lý T Turing-hoàn thành (như các mẫu C + +)? –

1

Các <tgmath.h> tiêu đề, tiêu chuẩn hóa trong C năm 1999, cung cấp các cuộc gọi kiểu generic đến thói quen trong <math.h> và <complex.h>. Sau khi bạn bao gồm <tgmath.h>, văn bản nguồn "sin (x)" sẽ gọi sinl nếu x dài gấp đôi, sin nếu x là gấp đôi và sinf nếu x là phao.

Bạn vẫn sẽ cần phải điều kiện hóa các hằng số của mình, để bạn sử dụng "3.1" hoặc "3.1f" khi thích hợp. Có một loạt các kỹ thuật cú pháp cho điều này, tùy thuộc vào nhu cầu của bạn và những gì xuất hiện thẩm mỹ hơn cho bạn. Đối với các hằng số được biểu diễn chính xác trong độ chính xác nổi, bạn có thể chỉ cần sử dụng biểu mẫu nổi. Ví dụ: "y = .5f * x" sẽ tự động chuyển đổi .5f thành .5 nếu x là gấp đôi. Tuy nhiên, "sin (.5f)" sẽ tạo ra sinf (.5f), ít chính xác hơn sin (.5).

Bạn có thể có thể làm giảm conditionalization đến một định nghĩa rõ ràng duy nhất:

#if defined USE_FLOAT 
    typedef float Float; 
#else 
    typedef double Float; 
#endif 

Sau đó, bạn có thể sử dụng hằng theo những cách như thế này:

const Float pi = 3.14159265358979323846233; 
Float y = sin(pi*x); 
Float z = (Float) 2.71828182844 * x; 

Điều đó có thể không hoàn toàn hài lòng vì có là những trường hợp hiếm hoi khi một chữ số được chuyển đổi thành gấp đôi và sau đó thả nổi là kém chính xác hơn một chữ số được chuyển đổi trực tiếp thành phao. Vì vậy, bạn có thể tốt hơn với macro được mô tả ở trên, trong đó "C (chữ số)" nối thêm hậu tố cho số nếu cần.

+0

Cảm ơn con trỏ tới tgmath.h. Tôi vui mừng khi biết nó tồn tại, nhưng sử dụng thông tin kiểu để chọn chức năng nào được gọi? Làm thế nào mà điều đó biến nó thành C99? –

Các vấn đề liên quan