I am developing a library of special-purpose math functions in C. I need to provide a capability for the library to handle both single-precision and double-precision. The important point here is that the "single" functions should use ONLY "single" arithmetic internally (resp. for the "double" functions).
As an illustration, take a look at LAPACK (Fortran), which provides two versions of each of its function (SINGLE and DOUBLE). Also the C math library (example, expf and exp).
To clarify, I want to support something similar to the following (contrived) example:
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);
}
I've thought about the following approaches:
Using macros for the function name. This still requires two separate source codebases:
#ifdef USE_FLOAT
#define MYFUNC MyFloatFunc
#else
#define MYFUNC MyDoubleFunc
#endif
Using macros for the floating point types. This allows me to share the codebase across the two different versions:
#ifdef USE_FLOAT
#define NUMBER float
#else
#define NUMBER double
#endif
Just developing two separate libraries, and forgetting about trying to save headaches.
Does anyone have a recommendation or additional suggestions?
For polynomial approximations, interpolations, and other inherently approximative math functions, you cannot share code between a double-precision and a single-precision implementation without either wasting time in the single-precision version or being more approximative than necessary in the double-precision one.
Nevertheless, if you go the route of the single codebase, the following should work for constants and standard library functions:
#ifdef USE_FLOAT
#define C(x) x##f
#else
#define C(x) x
#endif
... C(2.0) ... C(sin) ...
(Partially inspired by Pascal Cuoq's answer)
If you want one library with float and double versions of everything, you could use recursive #include
s in combination with macros. It doesn't result in the clearest of code, but it does let you use the same code for both versions, and the obfuscation is thin enough it's probably manageable:
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
Each file #include
s itself one additional time, using different macro definitions on the second pass, to generate two versions of the code that uses the macros.
Some people may object to this approach, though.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With