/* * * Copyright (C) 2014-2021, OFFIS e.V. * All rights reserved. See COPYRIGHT file for details. * * This software and supporting documentation were developed by * * OFFIS e.V. * R&D Division Health * Escherweg 2 * D-26121 Oldenburg, Germany * * * Module: config * * Author: Jan Schlamelcher * * Purpose: Query properties of fundamental arithmetic types * for being used within oflimits.h. */ // Note: This depends on some files of ofstd and osconfig.h, // although it is part of configure testing itself. // Therefore, ensure osconfig.h has already been generated // before this program is used. #include "../math.cc" #ifdef HAVE_FENV_H // For controlling floating point exceptions on Unix like systems. #include #endif #ifdef HAVE_IEEEFP_H // For controlling floating point exceptions on Unix like systems // that don't have feenableexcept and fedisableexcept. #include #endif #if defined(__APPLE__) && !defined(__aarch64__) // For controlling floating point exceptions on OS X. #include #endif #ifdef HAVE_SETJMP_H // For setjmp for the SunPro compiler #include #endif #ifdef HAVE_SIGNAL_H // For signal for the SunPro compiler #include #endif // hackish definition of cout, as we can't depend on // OFConsole. #define COUT STD_NAMESPACE cout // define sigjmp_buf if it isn't already #ifndef HAVE_SIGJMP_BUF typedef jmp_buf sigjmp_buf; #endif // longjmp env (jump destination), ugly global var we // need to recover from traps. sigjmp_buf jbuf; // signal handler that "jumps back in time" to before // the error occurred and fixes what has gone wrong // in 1985. static void capture( int sig ) { #ifndef _WIN32 // Unix like systems support this natively siglongjmp( jbuf, sig ); #else // On Windows, we need to employ a workaround that // only works for floating point exceptions! _fpreset(); longjmp( jbuf, sig ); #endif } static void register_signals() { signal(SIGABRT, &capture); signal(SIGFPE, &capture); signal(SIGILL, &capture); signal(SIGINT, &capture); signal(SIGSEGV, &capture); signal(SIGTERM, &capture); } // print to "arith.h" static void print_define( STD_NAMESPACE ostream& out, const char* fname, const char* name ) { out << "#define DCMTK_"; // convert type name to uppercase and replace ' ' by underscore. for( const char* pch = name; *pch; ++pch ) out.put( *pch <= 'z' && *pch >= 'a' ? *pch - 'a' + 'A' : *pch == ' ' ? '_' : *pch ); out << '_' << fname; } // handles output to configure log and to "arith.h" static int print_flag( STD_NAMESPACE ostream& out, int flag, const char* fname, const char* name, int width ) { COUT << STD_NAMESPACE setw(width) << STD_NAMESPACE internal << ( flag ? "yes" : "no" ); print_define( out, fname, name ); out << " OF" << ( flag ? "True" : "False" ) << '\n'; return flag; } static const char all_one[8] = { '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377' }; template static int test_modulo() { // test if signed, otherwise it is modulo anyway if( *OFreinterpret_cast( const T*, all_one ) > 0 ) return 1; const T max = *OFreinterpret_cast( const T*, all_one ) & ~(T(1) << (sizeof(T) * 8 - 1)); const T min = T( max + 1 ); return min < max && min < 0 && max > 0; } template static int test_trap( const FN& fn ) { register_signals(); #ifndef _WIN32 // On Unix like systems, we use the // longjmp-module specifically designed for // signals. if( sigsetjmp( jbuf, 1 ) ) #else // On Windows, we use the normal longjmp // that destroys everything and try to fix // what is left. if( setjmp( jbuf ) ) #endif { // if a jump occurred, this means fn() // caused a trap. return 1; } #ifdef _MSC_VER // On Visual Studio, we use their built-in // SEH things, they consider to be C++. __try #else // The rest of the world hopefully only // has normal exceptions. try #endif { // Call the function. Did it trap? fn(); } #ifdef _MSC_VER // On Windows, we return 3 if any SEH exception // occurred. Shall we consider this a trap, // although it was really an exception? // I don't know, for now it's yes. __except(1){return 3;} #else // The rest of the world returns 2, which // means "normal exception". catch(...){return 2;} #endif // Nothing happened, so we return 0, for // "nothing happened". Note: since MinGW's // crazy SEH things are crazy, MinGW will always // execute what is written in the brackets following // __except1, and NEVER see this return statement. return 0; } template static void divide_by_zero() { // Note: we can't just do 1 / 0, since // some compilers might optimize that // away or emit compile-time errors. volatile T t0 = 1; volatile T t1 = 0; #ifndef DCMTK_UNDEF_SANITIZER // disable this line when compiling with gcc -fsanitize=undefined // by defining DCMTK_UNDEF_SANITIZER because otherwise this // program will be aborted with a runtime error, thus preventing // CMake from creating the makefiles / project files. t0 /= t1; #endif } // gathers and prints information for integer types template static void inspect( STD_NAMESPACE ostream& out, const char* name ) { COUT << "-- " << STD_NAMESPACE left << STD_NAMESPACE setw(14) << name; print_flag( out, test_trap( ÷_by_zero ), "TRAPS", name, 7 ); print_flag( out, test_modulo(), "MODULO", name, 7 ); COUT << OFendl; } // print a constant's binary representation as a string, so it can be loaded // instead of being calculated when needed template static void print_binary( STD_NAMESPACE ostream& out, const char* pname, const char* name, const T& t ) { print_define( out, pname, name ); out << " *OFreinterpret_cast( const " << name << "*, \""; for( unsigned ui = 0; ui < sizeof(T); ++ui ) out << '\\' << STD_NAMESPACE oct << STD_NAMESPACE setw(3) << STD_NAMESPACE right << STD_NAMESPACE setfill( '0' ) << unsigned(((unsigned char*)&t)[ui]) << STD_NAMESPACE dec; out << "\" )\n"; } // try to create an infinite value and use whatever means // to detect if it really is infinite template static int test_inf( STD_NAMESPACE ostream& out, const char* name ) { const T t( HUGE_VAL ); const int has_inf = print_flag ( out, dcmtk_config_math::isinf(t), "HAS_INFINITY", name, 7 ); print_binary( out, "INFINITY", name, t ); return has_inf; } template static T guess_qnan() { #ifdef NAN // use NAN if available return T( NAN ); #else // else do a barrel roll T t( 0.0 ); return t / t; #endif } template static int test_qnan( STD_NAMESPACE ostream& out, const char* name ) { const T t = guess_qnan(); const int has_qnan = print_flag ( out, dcmtk_config_math::isnan( t ), "HAS_QUIET_NAN", name, 7 ); print_binary( out, "QUIET_NAN", name, t ); return has_qnan; } template struct guess; template<> struct guess { // There is no way to create a signaling NaN employing only arithmetic // operations. So we assume we have IEC-559 floating point types // and convert the quiet NaNs we created to signaling NaNs by flipping // the bits that would need to be flipped if it really were IEC-559 floats. static const float& snan() { static float f = guess_qnan(); ((unsigned char*)&f)[2] &= 0xbf; ((unsigned char*)&f)[0] |= 0x01; return f; } static float denorm_min() { return float( ldexp( 1.0f, FLT_MIN_EXP - FLT_MANT_DIG ) ); } static float denorm_max() { return FLT_MIN / 2; } static float epsilon() { return FLT_EPSILON; } }; template<> struct guess { // There is no way to create a signaling NaN employing only arithmetic // operations. So we assume we have IEC-559 floating point types // and convert the quiet NaNs we created to signaling NaNs by flipping // the bits that would need to be flipped if it really were IEC-559 floats. static const double& snan() { static double d = guess_qnan(); ((unsigned char*)&d)[6] &= 0xf7; ((unsigned char*)&d)[0] |= 0x01; return d; } static double denorm_min() { return double( ldexp( 1.0, DBL_MIN_EXP - DBL_MANT_DIG ) ); } static double denorm_max() { return DBL_MIN / 2; } static double epsilon() { return DBL_EPSILON; } }; template static void provoke_snan() { // Enable floating point exceptions, Windows and Unix version. #ifdef HAVE_WINDOWS_H _clearfp(); _controlfp( _controlfp(0,0) & ~_EM_INVALID, _MCW_EM ); #elif defined(__APPLE__) && !defined(__aarch64__) _MM_SET_EXCEPTION_MASK( _MM_GET_EXCEPTION_MASK() & ~_MM_MASK_INVALID ); #elif defined(HAVE_FENV_H) && defined(HAVE_PROTOTYPE_FEENABLEEXCEPT) feenableexcept( FE_INVALID ); #elif defined(HAVE_IEEEFP_H) && !defined(__CYGWIN__) // Cygwin unfortunately seems to have but no implementation of fgetmask/fpsetmask #ifdef HAVE_DECLARATION_FP_EXCEPT_T fp_except_t cw = fpgetmask(); #else fp_except cw = fpgetmask(); #endif #ifdef FP_X_DX // on some systems, the devide-by-zero flag is called FP_X_DX fpsetmask(cw | FP_X_INV | FP_X_DX | FP_X_OFL); #else fpsetmask(cw | FP_X_INV | FP_X_DZ | FP_X_OFL); #endif #endif // Visual Studio will emit an exception the moment // we assign a signaling NaN to another float variable // and convert it to a quiet NaN. volatile T t = guess::snan(); // Other compilers will trigger only if we use arithmetics. #ifndef DCMTK_UNDEF_SANITIZER // disable this line when compiling with gcc -fsanitize=undefined // by defining DCMTK_UNDEF_SANITIZER because otherwise this // program will be aborted with a runtime error, thus preventing // CMake from creating the makefiles / project files. ++t; #endif } template static int test_snan( STD_NAMESPACE ostream& out, const char* name ) { // Create signaling NaNs and test if they really are signaling NaNs const T& t = guess::snan(); int flag = dcmtk_config_math::isnan( t ) && test_trap( &provoke_snan ); // Disable floating point exceptions that have been enabled for the test, // Windows and Unix version. #ifdef HAVE_WINDOWS_H _clearfp(); _controlfp( _controlfp(0,0) | _EM_INVALID, _MCW_EM ); #elif defined(HAVE_FENV_H) feclearexcept( FE_INVALID ); #if defined(__APPLE__) && !defined(__aarch64__) _MM_SET_EXCEPTION_MASK( _MM_GET_EXCEPTION_MASK() | _MM_MASK_INVALID ); #elif defined(HAVE_FENV_H) && defined(HAVE_PROTOTYPE_FEENABLEEXCEPT) fedisableexcept( FE_INVALID ); #elif defined(HAVE_IEEEFP_H) && !defined(__CYGWIN__) // Cygwin unfortunately seems to have but no implementation of fgetmask/fpsetmask #ifdef HAVE_DECLARATION_FP_EXCEPT_T fp_except_t cw = fpgetmask(); #else fp_except cw = fpgetmask(); #endif #ifdef FP_X_DX // on some systems, the devide-by-zero flag is called FP_X_DX fpsetmask(cw & ~(FP_X_INV | FP_X_DX | FP_X_OFL)); #else fpsetmask(cw & ~(FP_X_INV | FP_X_DZ | FP_X_OFL)); #endif #endif #endif // Print and return the result print_flag ( out, flag, "HAS_SIGNALING_NAN", name, 7 ); print_binary( out, "SIGNALING_NAN", name, t ); return flag; } template static void test_iec559( STD_NAMESPACE ostream& out, const char* name, int reqs ) { // Assume we have IEC-559 floats if the appropriate macro was defined, // or if infinity and both kinds of NaNs are supported and the characteristics // of double are what we expect them to be. print_flag ( out, #if defined(__STDC_IEC_559__) || (DBL_DIG == 15 && DBL_MANT_DIG == 53 && DBL_MAX_10_EXP == 308 && DBL_MAX_EXP == 1024 && DBL_MIN_10_EXP == -307 && DBL_MIN_EXP == -1021) reqs, #else 0, #endif "IS_IEC559", name, 7 ); } static int is_subnormal( float f ) { // Wine does not implement _fpclassf, so don't use it when cross compiling for Windows // (Wine would emit an exception at runtime, so it's hard to test for _fpclassf availability) #if defined(HAVE_PROTOTYPE__FPCLASSF) && !( defined(DCMTK_CROSS_COMPILING) && defined(_WIN32) ) return ( _fpclassf( f ) & ( _FPCLASS_ND | _FPCLASS_PD ) ) != 0; #else return f != 0.0f && ( f < 0.0f ? -f < FLT_MIN : f < FLT_MIN ); #endif } static int is_subnormal( double d ) { #ifdef _WIN32 return ( _fpclass( d ) & ( _FPCLASS_ND | _FPCLASS_PD ) ) != 0; #else return d != 0.0 && ( d < 0.0 ? -d < DBL_MIN : d < DBL_MIN ); #endif } template static void test_subnormal( STD_NAMESPACE ostream& out, const char* name ) { #ifdef HAVE_WINDOWS_H _clearfp(); _controlfp( _EM_DENORMAL | _EM_UNDERFLOW | _EM_INEXACT, _MCW_EM ); #endif const T denorm = guess::denorm_min(); const int flag = is_subnormal( denorm ); COUT << STD_NAMESPACE setw(7) << STD_NAMESPACE internal << ( flag ? "yes" : "no" ); print_define( out, "HAS_DENORM", name ); out << " OFdenorm_" << ( flag ? "pre" : "ab" ) << "sent\n"; print_binary( out, "DENORM_MIN", name, denorm ); } template static void test_tinyness_before( STD_NAMESPACE ostream& out, const char* name ) { volatile const T denorm_max = guess::denorm_max(); volatile const T mult = T( 1.0 ) + guess::epsilon(); #ifdef HAVE_WINDOWS_H _clearfp(); #elif defined(HAVE_FENV_H) feclearexcept( FE_ALL_EXCEPT ); #endif volatile T result = denorm_max * mult; (void)result; print_flag ( out, #ifdef HAVE_WINDOWS_H _statusfp() & _EM_UNDERFLOW, #elif defined(HAVE_FENV_H) fetestexcept( FE_UNDERFLOW ), #else 0, #endif "TINYNESS_BEFORE", name, 7 ); } template static void test_denorm_loss( STD_NAMESPACE ostream& out, const char* name ) { volatile T t = guess::denorm_min() * 3; #ifdef HAVE_WINDOWS_H _clearfp(); #elif defined(HAVE_FENV_H) feclearexcept( FE_ALL_EXCEPT ); #endif t /= 2; print_flag ( out, #ifdef HAVE_WINDOWS_H _statusfp() & _EM_UNDERFLOW, #elif defined(HAVE_FENV_H) fetestexcept( FE_UNDERFLOW ), #else 0, #endif "HAS_DENORM_LOSS", name, 7 ); } #ifdef HAVE_WINDOWS_H // MinGW's crazy SEH stuff doesn't work on all MinGW "distributions" // This is an even more ugly fallback implementation that seems to // work for now LONG WINAPI consume_seh_problems( struct _EXCEPTION_POINTERS* ) { _fpreset(); _clearfp(); longjmp( jbuf, 0 ); return EXCEPTION_CONTINUE_SEARCH; } #endif int main( int argc, char** argv ) { #ifdef HAVE_WINDOWS_H // Activate the fallback workaround, it will only be used // if the SEH exceptions can't be caught "the right way" SetErrorMode( SEM_FAILCRITICALERRORS ); SetUnhandledExceptionFilter( consume_seh_problems ); #endif COUT << "Inspecting fundamental arithmetic types... " << OFendl; if( argc != 2 ) { STD_NAMESPACE cerr << "-- " << "Error: missing destination file " << "to store collected information." << OFendl; return 1; } STD_NAMESPACE ofstream out( argv[1] ); out << "#ifndef CONFIG_ARITH_H" << '\n'; out << "#define CONFIG_ARITH_H" << '\n'; out << '\n'; // workaround to let digits10 be a compile time constant out << "#define DCMTK_SIGNED_CHAR_DIGITS10 " << OFstatic_cast( int, ( CHAR_BIT - 1 ) * .30102999566398119521373889472449 ) << OFendl; out << "#define DCMTK_UNSIGNED_CHAR_DIGITS10 " << OFstatic_cast( int, CHAR_BIT * .30102999566398119521373889472449 ) << OFendl; out << "#define DCMTK_SIGNED_SHORT_DIGITS10 " << OFstatic_cast( int, ( CHAR_BIT * sizeof( signed short ) - 1 ) * .30102999566398119521373889472449 ) << OFendl; out << "#define DCMTK_UNSIGNED_SHORT_DIGITS10 " << OFstatic_cast( int, CHAR_BIT * sizeof( unsigned short ) * .30102999566398119521373889472449 ) << OFendl; out << "#define DCMTK_SIGNED_INT_DIGITS10 " << OFstatic_cast( int, ( CHAR_BIT * sizeof( signed int ) - 1 ) * .30102999566398119521373889472449 ) << OFendl; out << "#define DCMTK_UNSIGNED_INT_DIGITS10 " << OFstatic_cast( int, CHAR_BIT * sizeof( unsigned int ) * .30102999566398119521373889472449 ) << OFendl; out << "#define DCMTK_SIGNED_LONG_DIGITS10 " << OFstatic_cast( int, ( CHAR_BIT * sizeof( signed long ) - 1 ) * .30102999566398119521373889472449 ) << OFendl; out << "#define DCMTK_UNSIGNED_LONG_DIGITS10 " << OFstatic_cast( int, CHAR_BIT * sizeof( unsigned long ) * .30102999566398119521373889472449 ) << OFendl; out << "#define DCMTK_FLOAT_MAX_DIGITS10 " << OFstatic_cast( int, FLT_MANT_DIG * .30102999566398119521373889472449 + 2 ) << OFendl; out << "#define DCMTK_DOUBLE_MAX_DIGITS10 " << OFstatic_cast( int, DBL_MANT_DIG * .30102999566398119521373889472449 + 2 ) << OFendl; COUT << "--" << OFendl; COUT << "-- " << STD_NAMESPACE setfill( ' ' ) << STD_NAMESPACE setw(17) << ' ' << STD_NAMESPACE setw(7) << STD_NAMESPACE internal << "TRAPS" << STD_NAMESPACE setw(7) << STD_NAMESPACE internal << "MODULO" << OFendl; inspect( out, "char" ); inspect( out, "signed char" ); inspect( out, "unsigned char" ); inspect( out, "signed short" ); inspect( out, "unsigned short" ); inspect( out, "signed int" ); inspect( out, "unsigned int" ); inspect( out, "signed long" ); inspect( out, "unsigned long" ); COUT << "--" << OFendl; COUT << "-- " << STD_NAMESPACE setfill( ' ' ) << STD_NAMESPACE setw(18) << ' ' << STD_NAMESPACE setw(7) << STD_NAMESPACE internal << "float" << STD_NAMESPACE setw(7) << STD_NAMESPACE internal << "double" << OFendl; COUT << STD_NAMESPACE setw(20) << STD_NAMESPACE left << "-- TRAPS"; print_flag( out, test_trap( ÷_by_zero ), "TRAPS", "float", 7 ); print_flag( out, test_trap( ÷_by_zero ), "TRAPS", "double", 7 ); COUT << OFendl; COUT << STD_NAMESPACE setw(20) << STD_NAMESPACE left << "-- HAS INFINITY"; const int finf = test_inf( out, "float" ); const int dinf = test_inf( out, "double" ); COUT << OFendl; COUT << STD_NAMESPACE setw(20) << STD_NAMESPACE left << "-- QUIET NAN"; const int fqnan = test_qnan( out, "float" ); const int dqnan = test_qnan( out, "double" ); COUT << OFendl; COUT << STD_NAMESPACE setw(20) << STD_NAMESPACE left << "-- SIGNALING NAN"; const int fsnan = test_snan( out, "float" ); const int dsnan = test_snan( out, "double" ); COUT << OFendl; COUT << STD_NAMESPACE setw(20) << STD_NAMESPACE left << "-- IEC-559"; test_iec559( out, "float", finf && fqnan && fsnan ); test_iec559( out, "double", dinf && dqnan && dsnan ); COUT << OFendl; COUT << STD_NAMESPACE setw(20) << STD_NAMESPACE left << "-- HAS DENORM"; test_subnormal( out, "float" ); test_subnormal( out, "double" ); COUT << OFendl; COUT << STD_NAMESPACE setw(20) << STD_NAMESPACE left << "-- TINYNESS BEFORE"; test_tinyness_before( out, "float" ); test_tinyness_before( out, "double" ); COUT << OFendl; COUT << STD_NAMESPACE setw(20) << STD_NAMESPACE left << "-- DENORM LOSS"; test_denorm_loss( out, "float" ); test_denorm_loss( out, "double" ); COUT << OFendl; COUT << "--"; out << "#define DCMTK_ROUND_STYLE " << FLT_ROUNDS << '\n'; out << '\n'; out << "#endif // CONFIG_ARITH_H" << '\n'; return 0; }