C++ Boost

Mathematical Constants

Overview


Introduction
File Formats
Header file naming convention
Which constants?
How to use C-style MACRO files?
C++ float, double and long double
Multiple Precision Constants
Avoiding name collisions

Introduction

These files are a collection of very accurate mathematical constants for C ++ and C programs.
These are like to be useful even by programs just computing the circumference of circles!

A special feature of this collection is that the constants values are a little more accurate (40 decimal digits) than can be represented by current, and currently foreseeable, floating-point hardware.

The mathematical constants collection is:

The constants have all been calculated using high-precision software working with up to 400-bit precision giving about 130 decimal digits. (The precision can be arbitrarily chosen and is limited only by compute time). The accuracy selected (40 decimal digits) exceeds the accuracy of reasonably foreseeable floating-point hardware (128-bit).

The objective is to achieve the full accuracy possible for all real-life computations. This has no extra cost to the user, but reduces irritating, and often confusing and very hard-to-trace effects, caused by the intrinsically limited precision of floating-point calculations. The slightest symptom is a spurious least-significant digit; at worst slightly inaccurate constants sometimes cause iterating algorithms to diverge wildly because internal comparisons just fail.

Which constants?

The choice of constants is necessarily arbitrary and inevitably contentious.  The constants chosen are widely used in calculation of mathematical functions and include most of those used by several authors of mathematical function libraries or texts referenced, for example, Knuth, Cody, Morris, Hart and Moshier.

The first type contains the most fundamental constants that cannot be easily calculated, like pi and e.
The second type contains derived constants that are widely used.
Although compilers may calculate them at compile time, some compilers may lose some accuracy for some constants.
Some can probably be calculated without loss (for example, 2. * pi) but are commonly used and so are convenient.
Derived types like log(pi) can only be calculated at runtime, and often with significant loss of accuracy.

Some upper and lower interval limits are especially at risk of being inaccurately calculated using limits of more fundamental constants, with multiplication and division by factors of the radix the only safe exception.  Explicitly calculated exactly representable values are safer, but are necessarily dependent on the floating point format, radix and significand.

File Formats

Different applications need constants in different formats.  Six types of file are provided:

1.     Files which contain C Macros which #define constants and matching #undefs. These are less likely to be useful for C++ programs, but may suit C programs or embedded systems where no inefficiency in using memory can be tolerated.

2.     Files that contain C++ float const constants. Useful if only float precision is required.

3.     Files that contain C++ double const constants. Most useful for most purposes using C++ double precision.

4.     Files that contain C++ long double const constants. Useful where long double precision is needed.

5.     Files that contain C++ constants defined as C++ functions, so called by pi(), to help compilers inline and optimise for speed.

6.     Files that contain C++ class constant template function constants, but called by pi as well as pi(). This form is useful where more than one precision (for example, both float and double) is needed, and to facilitate optimisation for speed by compilers.

7.     Files that contain C++ exactly representable upper and lower limits of the smallest interval containing the constant. The limits differ for different floating-point formats depending on the radix or base, and number of significand (or mantissa) bits. The limits are selected, by default, using std::numeric_limits<floating_point_type>::digits and std::numeric_limits<floating_point_type>::radix values, if available.

More than one header file may be used, but including two files in the same module will almost certainly lead to confusing results.  This might happen implicitly by including a header that in turn includes another constants header file.  No protection against this is provided and may result in baffling compilation errors and/or unexpected selection of constant representation. You have been warned!

All files contain exactly the same constants and identical numeric values with 40 decimal digits (except interval values which have enough decimal digits to be exactly representable).

Header file naming convention

The followiing files are provided (zipped):

How to use C-style MACRO files?

The file define_constants.h consists of a list of C style MACROs that define a collection of mathematical constants very accurately, for example:

 #define BOOST_PI  3.14159265358979323846264338327950288L /* pi */

Individual MACRO value(s) can be used directly, for example:

  long double const PI = BOOST_PI;

Or with casting to other types, for example:

   double const PI = (long double)BOOST_PI;

 (Note that to avoid any loss of precision for long double definitions, the MACRO constants are defined as long double by the suffix L, but this means that casting is needed or desirable to avoid errors or warnings for less precise types double or float.)

  double const pi = (double)BOOST_PI; /* C style cast */

Or C++ static cast down to double and/or float precisions.

long double const pi = static_cast<double>(BOOST_PI); // C++ static_cast
long float const pi_f = static_cast<float>(BOOST_PI); // C++ static_cast
long double const pi_f = static_cast<long double>(BOOST_PI);

Note: one needs two different names, pi and pi_f, if different precisions must coexist (unless one uses constant class template functions described below).

Unlike most header files, the macros file can be used in many ways in both C and C++ (and indeed in other languages).
(Note that is it deliberately a .h file and not the Boost convention of a .hpp file.  This is to aid its use in C programs, as well as C++ programs).

Corresponding files that contain matching #undef statements called undefine*.h are also provided to reduce namespace pollution and the risk of name clashes or confusion.

C++ float, double and long double

Files (of type .hpp) containing constants as const float, const double and const long double values can be #included or, dare one suggest, individual literal value(s) strings can be copied and pasted into programs, for example:

float const pi = 3.14159265358979323846264338327950288F;
(Note that the suffix is F to avoid the need to static_cast long double to float).
long double const pi = 3.14159265358979323846264338327950288L;

(Note that the suffix is L because the literal should be a long double).

The header file constants.h can be included in a separate translation unit:

  #include “constants.h” // for C-style MACROs
  extern long double const pi = static_cast<double>(BOOST_PI); // C++ static cast
  extern long double const e = static_cast<double>(BOOST_E); // C++ static cast

will make only pi and e visible to other translation units through the linker.  The names can be chosen to avoid name clashes.
The simpler scheme defining constants within a translation unit.

long double const pi = static_cast<double>(BOOST_PI); // C++ static cast
may cause some name clashes, so a matching file undef_constants.h is provided to undefine ALL the macros defined by define_constants.h.  This undefined file should be included immediately after the last reference to any BOOST macro constant.

Multiple Precision Constants

When multiple precision constants are needed in the same program, (and namespace pollution is to be avoided) class constant template functions in files like function_constants.hpp can be used:

 // Select an entire set of constants with a single using directive:
using namespace boost::math::double_constants;
// Or float constants...
// using namespace boost::math::float_constants;

// So naive users (mea culpa) can just say "pi",
// and get boost::math::double_constants::pi.
cout << "pi = " << pi << endl;
double r = 2.;
cout << "area = " << pi * r * r << "\n";

Naughty users CAN'T write pi = 3.; !!! // - they get a cryptic error message:
(See also House Bill No. 246, Indiana State Legislature, Urban Legends.)

 // C2678: binary '=' : no operator defined which takes a left-hand operand of type
// 'const struct boost::math::constant<struct boost::math::pi_tag,double>'
// Or they can explicitly access fully specified constants at any time:
cout.precision(ceil(1 + std::numeric_limits<double>::digits*log10(2.)));
// Ensure all significant digits for double are displayed, or
cout << "double pi = " << boost::math::double_constants::pi << endl;
 
cout.precision(std::numeric_limits<double>::digits10);
// Ensure all guaranteed digits for double are displayed.
 
cout << "double pi = " << boost::math::double_constants::pi << endl;
 
cout.precision(ceil(1 +std::numeric_limits<long double>::digits) *log10(2.)) ;
cout << "long double pi = " << boost::math::long_double_constants::pi << "\n";
 
using namespace boost::math;
  cout.precision(ceil(1 + long_double_significand_digits * log10(2.));
cout << "float pi = " << float_constants::pi << endl;
cout.precision(ceil(1+std::numeric_limits<double>::digits10)*log10(2.));
cout << "double pi = " << double_constants::pi << endl;

// But CANNOT switch to float constants.
// using namespace boost::math::float_constants;
// cout << "pi = " << pi << endl; // error C2872: 'pi' : ambiguous symbol
// This is probably a useful safeguard.

  // For types that are not specialised, one could write:
const short pi_I = (short)boost::math::float_constants::pi;
cout << "short pi = " << pi_I << endl; // 3!
// Access via the templated function call may allow better optimisation.
float pi_float = boost::math::constant<pi_tag, float >();

// If a template is not specialised, then it fails at link time.

Michael Kenniston devised this scheme (after much discussion on Boost) for the following reasons:

  1. Only need to write one interface (not counting macros-for-C, which is really a separate library from the C++ stuff)

  2. No macros: all names are in namespaces.

  3.  Average users can switch "pi" between float and double by changing one "using" directive.

  4. No "double rounding" (i.e. no multiple conversions).

  5.  Platform implementers can use whatever evil incantations they desire for maximum accuracy.

  6. Everything is implemented with inline functions to ease constant folding.

  7. It is (relatively) easy to extend with new constants and representation types.

  8. Purists can access all constants via templated functions.

  9. No "static-initialization-order" issues, because the static variables contain no data to initialise.

  10. No partial template specialization, so it works on MSVC.

  11. No non-type template parameters, so it should work on Borland.

(The same basic techniques will probably work for converting read access to any statically allocated class object to an inline function call, including in particular physical constants with units/dimensions).

The disadvantage of this method is that some compilers in strict mode seem to insist on explicit definition of the empty class constant and this leads to much clutter and potential code-bloat because a constructor is instantiated for each and every of the many constants. Some compilers are able to optimise these empty and unused constructors away, but may not do so in debug mode.  Other compilers (and some in non-strict mode) do not require the definition of the empty constant class.  The C++ Standard ISO 14882:1998 does not make clear whether this is a language ambiguity or an excessive compiler requirement.  However, the net result is that this method may not be ideally efficient or portable, at least at present.

Avoiding name collisions

C++ allows other methods of avoiding name collisions, for example using named and unnamed or anonymous namespaces.

C macros header files have corresponding 'undefine' files which may remove macro definitions like BOOST_PI from the global namespace.


http://hetp.u-net.com/public/ /overview.html   Revised 5 May 2005

© Copyright Paul A. Bristow 2002-2005. All Rights Reserved.