Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proper design of C code that handles both single- and double-precision floating point?

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:

  1. Using macros for the function name. This still requires two separate source codebases:

    #ifdef USE_FLOAT
    #define MYFUNC MyFloatFunc
    #else
    #define MYFUNC MyDoubleFunc
    #endif
    
  2. 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
    
  3. Just developing two separate libraries, and forgetting about trying to save headaches.

Does anyone have a recommendation or additional suggestions?

like image 399
David H Avatar asked Oct 27 '11 19:10

David H


2 Answers

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) ...
like image 134
Pascal Cuoq Avatar answered Sep 19 '22 19:09

Pascal Cuoq


(Partially inspired by Pascal Cuoq's answer) If you want one library with float and double versions of everything, you could use recursive #includes 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 #includes 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.

like image 37
Dmitri Avatar answered Sep 19 '22 19:09

Dmitri