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
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/make_constants_h ${CMAKE_CURRENT_BINARY_DIR}/constants.h
DEPENDS make_constants_h)
add_custom_target (constants_h ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/constants.h)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
# 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.
-Wall -Wextra -pedantic
) You could start fixing them by replacing themain()
signature statement with:int main( void )
\$\endgroup\$%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\$