...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
The most common example of a high-precision user-defined type will probably be Boost.Multiprecision.
The syntax for using the function-call constants with user-defined types is the same as it is in the template class, which is to say we use:
#include <boost/math/constants/constants.hpp> boost::math::constants::pi<UserDefinedType>();
For example:
boost::math::constants::pi<boost::multiprecision::cpp_dec_float_50>();
giving π with a precision of 50 decimal digits.
However, since the precision of the user-defined type may be much greater than that of the built-in floating point types, how the value returned is created is as follows:
float
and the type is constructible
from a float
then
our code returns a float
literal. If the user-defined type is a literal type then the function
call that returns the constant will be a constexp
.
double
and the type is constructible
from a double
then
our code returns a double
literal. If the user-defined type is a literal type then the function
call that returns the constant will be a constexp
.
long double
and the type is constructible from a long
double
then our code returns
a long double
literal. If the user-defined type is a literal type then the function
call that returns the constant will be a constexp
.
__float128
(and the compiler
supports such a type) and the type is constructible from a __float128
then our code returns
a __float128
literal.
If the user-defined type is a literal type then the function call
that returns the constant will be a constexp
.
boost::math::tools::digits<T>()
)
is less than 100 decimal digits, then the constant is constructed
"on the fly" from the string representation of the constant.
In addition, it is possible to pass a Policy
type as a second template argument, and use this to control the precision:
#include <boost/math/constants/constants.hpp> typedef boost::math::policies::policy<boost::math::policies::digits2<80> > my_policy_type; boost::math::constants::pi<MyType, my_policy_type>();
Note | |
---|---|
Boost.Math doesn't know how to control the internal precision of |
It is also possible to control which method is used to construct the constant
by specialising the traits class construction_traits
:
namespace boost{ namespace math{ namespace constant{ template <class T, class Policy> struct construction_traits { typedef std::integral_constant<int, N> type; }; }}} // namespaces
Where N takes one of the following values:
N |
Meaning |
---|---|
0 |
The precision is unavailable at compile time; either construct from a decimal digit string or calculate on the fly depending upon the runtime precision. |
1 |
Return a float precision constant. |
2 |
Return a double precision constant. |
3 |
Return a long double precision constant. |
4 |
Construct the result from the string representation, and cache the result. |
Any other value N |
Sets the compile time precision to N bits. |
In addition, for user-defined types that need special handling, it's possible
to partially-specialize the internal structure used by each constant. For
example, suppose we're using the C++ wrapper around MPFR mpfr_class
:
this has its own representation of Pi which we may well wish to use in place
of the above mechanism. We can achieve this by specialising the class template
boost::math::constants::detail::constant_pi
:
namespace boost{ namespace math{ namespace constants{ namespace detail{ template<> struct constant_pi<mpfr_class> { template<int N> static mpfr_class get(const std::integral_constant<int, N>&) { // The template param N is one of the values in the table above, // we can either handle all cases in one as is the case here, // or overload "get" for the different options. mpfr_class result; mpfr_const_pi(result.get_mpfr_t(), GMP_RNDN); return result; } }; }}}} // namespaces
Finally, since it can be tricky to diagnose what meta-programmed code is doing, there is a diagnostic routine that prints information about how this library will handle a specific type, it can be used like this:
#include <boost/math/constants/info.hpp> int main() { boost::math::constants::print_info_on_type<MyType>(); }
If you wish, you can also pass an optional std::ostream argument to the
print_info_on_type
function.
Typical output for a user-defined type looks like this:
Information on the Implementation and Handling of Mathematical Constants for Type class boost::math::concepts::real_concept Checking for std::numeric_limits<class boost::math::concepts::real_concept> specialisation: no boost::math::policies::precision<class boost::math::concepts::real_concept, Policy> reports that there is no compile type precision available. boost::math::tools::digits<class boost::math::concepts::real_concept>() reports that the current runtime precision is 53 binary digits. No compile time precision is available, the construction method will be decided at runtime and results will not be cached - this may lead to poor runtime performance. Current runtime precision indicates that the constant will be constructed from a string on each call.