I'm using some rather old C code (circa 1996, written in K&R style) to do robust geometric computations, available here, for a library I'm trying to write. In order to work properly, this code has to execute an initializer function before calling any of the included routines:

void exactinit()
  REAL half;
  REAL check, lastcheck;
  int every_other;

  every_other = 1;
  half = 0.5;
  epsilon = 1.0;
  splitter = 1.0;
  check = 1.0;
  /* Repeatedly divide `epsilon' by two until it is too small to add to    */
  /*   one without causing roundoff.  (Also check if the sum is equal to   */
  /*   the previous sum, for machines that round up instead of using exact */
  /*   rounding.  Not that this library will work on such machines anyway. */
  do {
    lastcheck = check;
    epsilon *= half;
    if (every_other) {
      splitter *= 2.0;
    every_other = !every_other;
    check = 1.0 + epsilon;
  } while ((check != 1.0) && (check != lastcheck));
  splitter += 1.0;

  /* Error bounds for orientation and incircle tests. */
  resulterrbound = (3.0 + 8.0 * epsilon) * epsilon;
  ccwerrboundA = (3.0 + 16.0 * epsilon) * epsilon;
  // SNIP, compute some more things just like this
  isperrboundC = (71.0 + 1408.0 * epsilon) * epsilon * epsilon;

The variables resulterrbound, ccwerrboundA, etc. are global variables that are set in this function and used to determine whether the result of a given geometric predicate can be trusted or not, due to floating point errors.

The problem I'm trying to address is that I don't want the user to have to call exactinit() or any other initialization procedures at the beginning of every program which uses my library just because I use this other guy's code. I've found a workaround which I would like some critique on.

The global data that exactinit computes is just some machine-dependent constants and a few derived quantities, i.e. it does not change from one program invocation to the next. What I've done instead is write a program which computes all of the same constants that exactinit does and writes them to a header file, which the main library sources then include.

/* make_constants_h.c */
error_bounds_t compute_error_bounds()
  // SNIP, it's more or less the same as exactinit but it packs
  // everything into a struct

void write_error_bounds(const char * filename, error_bounds_t err_bounds)
  FILE * fid = fopen(filename, "w");
  fprintf(fid, "#ifndef CONSTANTS_H\n");
  fprintf(fid, "#define CONSTANTS_H\n");

  fprintf(fid, "const double epsilon = %a;\n", err_bounds.epsilon);
  fprintf(fid, "const double resulterrbound = %a;\n", err_bounds.resulterrbound);
  // SNIP, write the other constants to the header file

int main(int argc, char ** argv)
   write_error_bounds(argv[1], compute_error_bounds());
   return 0;

In order to generate the header file and include it in the library, I use custom commands and targets in CMake:

add_executable (make_constants_h make_constants_h.c)
add_custom_command (OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/constants.h
  DEPENDS make_constants_h)
add_custom_target (constants_h ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/constants.h)

# SRCS is the list of all library sources, set elsewhere
add_library (predicates SHARED ${SRCS}) 
add_dependencies (predicates constants_h)

I probably could have done this with a simple Makefile, but I used CMake for everything anyway. Point is, I use the build system to invoke the program that generates the header file.

So, what I would like some feedback on is whether generating a header file is or isn't a good idea. Since initializing the program data requires executing a loop, I can't just use preprocessor defines for everything. (Strictly speaking I could just #define epsilon to be 1.0/2^53, but I'd like to preserve as much of the original code as possible.) I can't think of any libraries offhand that do this, but I find the thought of generating a short header file to be much more palatable than forcing the user to invoke some function at the beginning of every program.

All of the code for this is available here.

  • \$\begingroup\$ you could have the entry points in your library, which depend on those values being set properly, check a 'new' flag the is initialized to 'false' that, if false, calls the (hidden) function and sets the flag to 'true' That way, the user has to do nothing special and the function will always be called when necessary \$\endgroup\$ Commented Dec 19, 2015 at 0:18
  • \$\begingroup\$ Have you tried compiling the posted code? It does not compile. It does cause the compiler to output several warnings and errors. when compiling, always enable all the warnings, then fix them. (for gcc, at a minimum use: -Wall -Wextra -pedantic ) You could start fixing them by replacing the main() signature statement with: int main( void ) \$\endgroup\$ Commented Dec 19, 2015 at 0:23
  • 1
    \$\begingroup\$ Using the %a format conversion specifier results in a hex output of a double argument in this format: [-]0xh.hhhhp±; Probably not what you want to do. \$\endgroup\$ Commented Dec 19, 2015 at 0:42
  • \$\begingroup\$ I actually do want the output in hex in order to avoid any potential rounding in converting to/from decimal. The code compiles for me under clang but throws a bunch of warnings relating to standard usage in gcc, let me see if I can fix that -- thanks for the heads up! \$\endgroup\$ Commented Dec 19, 2015 at 1:19

1 Answer 1

  • You don't want to define objects in the .h file. Two .c files independently including constants.h would result in multiple definition error at link time. So, a constants.h should declare the variables as extern, and another autogenerated constants.c file shall define them.

  • The approach makes portability at the binary level questionable. The generated constants are only valid for a machine the code was compiled at.

  • You may be interested in how initialization problem is addressed in gcc.

  • \$\begingroup\$ I hadn't thought about the binary portability issue; that definitely rules out this approach. \$\endgroup\$ Commented Dec 19, 2015 at 1:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.