Search code examples
cfloating-pointprintfdoubleformat-string

How do you support both doubles and long doubles without changing format specifiers?


I was able to compile xnec2c with long double's by simply search-replacing double with long double. It calculates correctly, except anywhere that a format specifier is used because it wants %Lf... and of course GCC is barking quite a bit about -Wformat= after hacking it in with this one-liner:

perl -p -i -e 's/_Complex/complex/g; s/gdouble/double/g; s/([ \t(]|^)double/$1long double/g' src/*.[ch]

Ultimately the goal is to:

  1. Replaces all double's with typedef double xnec2c_double_t
  2. Provide a ./configure --enable-long-double option in configure.ac that adjusts the typedef to long double.
  3. Not break printf's and related format specifier-based functions (like sprintf, etc).

Question:

What are the best practices for making format specifiers work with compile-time selection of double vs long double by changing the typedef?

I could go put #ifdef's around every print, or #define SPEC_DOUBLE "%Lf" and string-concatenate as printf("foo=" SPEC_DOUBLE "\n", foo) but that seems almost as hackish as adding #ifdef's around every printf. A my_smart_printf macro could be an option if there is an elegant way to do it, but I've not come up with one.

Is there a "good" way to do this?

Nota bene:

  • I'd rather not switch to C++.
  • This question is not about autoconf.
  • I am the xnec2c maintainer.

Solution

  • How do you support both doubles and long doubles without changing format specifiers in C?

    • Convert the floating point argument to long double and use matching long double specifiers so the same format can be used in all cases.

      printf("%Lf\n", (long double) float_or_double_or_long_double_object);
      
    • Follow the PRI... example from <inttypes.h> and create your own specifier as in #define SPEC_DOUBLE "Lf". Note no "%".

      printf("%" SPEC_DOUBLE "\n", xnec2c_double_object);
      
    • Create a helper function to convert your special floating point into a string.

      extern char *xnec2c_to_str(char *dest, xnec2c_double_t x);
      //                     -        digits           .  frac  \0 
      #define xnec2c_STR_SZ (1 + (LDBL_MAX_10_EXP+1) + 1 + 6 + 1)
      #define xnec2c_TO_STR(x) (xnec2c_to_str((char[xnec2c_STR_SZ]){ 0 }, x)
      printf("%s\n", xnec2c_TO_STR(x));
      
    • And now for something completely different, consider Formatted print without the need to specify type matching specifiers.