...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
There are two kinds of constexpr
support in this library:
There are two backend types which are literals:
cpp_int_backend
where the Allocator parameter is type void
.
In addition, prior to C++14 the Checked parameter must be boost::multiprecision::unchecked
.
For example:
using namespace boost::multiprecision; constexpr float128 f = 0.1Q // OK, float128's are always literals in C++11 constexpr int128_t i = 0; // OK, fixed precision int128_t has no allocator. constexpr uint1024_t j = 0xFFFFFFFF00000000uLL; // OK, fixed precision uint1024_t has no allocator. constexpr checked_uint128_t k = 1; // OK from C++14 and later, not supported for C++11. constexpr checked_uint128_t k = -1; // Error, as this would normally lead to a runtime failure (exception). constexpr cpp_int l = 2; // Error, type is not a literal as it performs memory management.
There is also support for user defined-literals with cpp_int
- these are limited to unchecked, fixed precision cpp_int
's
which are specified in hexadecimal notation. The suffixes supported are:
Suffix |
Meaning |
---|---|
_cppi |
Specifies a value of type: |
_cppui |
Specifies a value of type: |
_cppiN |
Specifies a value of type |
_cppuiN |
Specifies a value of type |
In each case, use of these suffixes with hexadecimal values produces a constexpr
result.
Examples:
// Any use of user defined literals requires that we import the literal-operators into current scope first: using namespace boost::multiprecision::literals; // // To keep things simple in the example, we'll make our types used visible to this scope as well: using namespace boost::multiprecision; // // The value zero as a number<cpp_int_backend<4,4,signed_magnitude,unchecked,void> >: constexpr auto a = 0x0_cppi; // The type of each constant has 4 bits per hexadecimal digit, // so this is of type uint256_t (ie number<cpp_int_backend<256,256,unsigned_magnitude,unchecked,void> >): constexpr auto b = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_cppui; // // Smaller values can be assigned to larger values: int256_t c = 0x1234_cppi; // OK // // However, this only works in constexpr contexts from C++14 onwards: constexpr int256_t d = 0x1_cppi; // Compiler error in C++11, requires C++14 // // Constants can be padded out with leading zeros to generate wider types: constexpr uint256_t e = 0x0000000000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFF_cppui; // OK // // However, specific-width types are best produced with specific-width suffixes, // ones supported by default are `_cpp[u]i128`, `_cpp[u]i256`, `_cpp[u]i512`, `_cpp[u]i1024`. // constexpr int128_t f = 0x1234_cppi128; // OK, always produces an int128_t as the result. constexpr uint1024_t g = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccc_cppui1024; // OK, // always produces an uint1024_t as the result. // // If other specific-width types are required, then there is a macro for generating the operators for these. // The macro can be used at namespace scope only: // BOOST_MP_DEFINE_SIZED_CPP_INT_LITERAL(2048); // // Now we can create 2048-bit literals as well: constexpr auto h = 0xff_cppi2048; // h is of type number<cpp_int_backend<2048,2048,signed_magnitude,unchecked,void> > // // Finally, negative values are handled via the unary minus operator: // constexpr int1024_t i = -0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_cppui1024; // // Which means this also works: constexpr int1024_t j = -g; // OK: unary minus operator is constexpr.
The front end of the library is all constexpr
from C++14 and later. Currently there are only two backend types that are
constexpr
aware: __float128
and cpp_int.
More backends will follow at a later date.
Provided the compiler is GCC, type float128
support constexpr
operations
on all arithmetic operations from C++14, comparisons, abs
,
fabs
, fpclassify
,
isnan
, isinf
,
isfinite
and isnormal
are also fully supported, but
the transcendental functions are not.
The cpp_int
types support constexpr arithmetic, provided it is a fixed precision type
with no allocator. It may also be a checked integer: in which case a compiler
error will be generated on overflow or undefined behaviour. In addition the
free functions abs
, swap
, multiply
,
add
, subtract
,
divide_qr
, integer_modulus
, powm
,
lsb
, msb
,
bit_test
, bit_set
,
bit_unset
, bit_flip
, sqrt
,
gcd
, lcm
are all supported. Use of cpp_int
in this way requires either a C++2a compiler (one which supports std::is_constant_evaluated()
- currently only gcc-9 or clang-9 or later),
or GCC-6 or later in C++14 mode. Compilers other than GCC and without std::is_constant_evaluated()
will support a very limited set of operations:
expect to hit roadblocks rather easily.
See compiler
support for std::is_constant_evaluated
;
For example given:
#include <boost/math/constants/constants.hpp> // For constant pi with full precision of type T. // using boost::math::constants::pi; template <class T> inline constexpr T circumference(T radius) { return 2 * boost::math::constants::pi<T>() * radius; } template <class T> inline constexpr T area(T radius) { return boost::math::constants::pi<T>() * radius * radius; }
We can now calculate areas and circumferences, using all compile-time constexpr
arithmetic:
using boost::multiprecision::float128; constexpr float128 radius = 2.25; constexpr float128 c = circumference(radius); constexpr float128 a = area(radius); std::cout << "Circumference = " << c << std::endl; std::cout << "Area = " << a << std::endl;
Note that these make use of the numeric constants from the Boost.Math
constants library, which also happen to be constexpr
.
These usually have the full precision of the floating-point type, here 128-bit,
about 36 decimal digits.
For a more interesting example, in constexpr_float_arithmetic_examples.cpp
we define a simple class for constexpr
polynomial arithmetic:
template <class T, unsigned Order> struct const_polynomial;
Given this, we can use recurrence relations to calculate the coefficients for various orthogonal polynomials - in the example we use the Hermite polynomials. Only the constructor does any work - it uses the recurrence relations to calculate the coefficient array:
template <class T, unsigned Order> class hermite_polynomial { const_polynomial<T, Order> m_data; public: constexpr hermite_polynomial() : m_data(hermite_polynomial<T, Order - 1>().data() * const_polynomial<T, 1>{0, 2} - hermite_polynomial<T, Order - 1>().data().derivative()) { } constexpr const const_polynomial<T, Order>& data() const { return m_data; } constexpr const T& operator[](std::size_t N)const { return m_data[N]; } template <class U> constexpr T operator()(U val)const { return m_data(val); } };
Now we just need to define H0 and H1 as termination conditions for the recurrence:
template <class T> class hermite_polynomial<T, 0> { const_polynomial<T, 0> m_data; public: constexpr hermite_polynomial() : m_data{1} {} constexpr const const_polynomial<T, 0>& data() const { return m_data; } constexpr const T& operator[](std::size_t N) const { return m_data[N]; } template <class U> constexpr T operator()(U val) { return m_data(val); } }; template <class T> class hermite_polynomial<T, 1> { const_polynomial<T, 1> m_data; public: constexpr hermite_polynomial() : m_data{0, 2} {} constexpr const const_polynomial<T, 1>& data() const { return m_data; } constexpr const T& operator[](std::size_t N) const { return m_data[N]; } template <class U> constexpr T operator()(U val) { return m_data(val); } };
We can now declare H9 as a constexpr
object, access the coefficients, and evaluate at an abscissa value, all at
compile-time using constexpr
arithmetic:
constexpr hermite_polynomial<float128, 9> h9; // // Verify that the polynomial's coefficients match the known values: // static_assert(h9[0] == 0); static_assert(h9[1] == 30240); static_assert(h9[2] == 0); static_assert(h9[3] == -80640); static_assert(h9[4] == 0); static_assert(h9[5] == 48384); static_assert(h9[6] == 0); static_assert(h9[7] == -9216); static_assert(h9[8] == 0); static_assert(h9[9] == 512); // // Define an abscissa value to evaluate at: constexpr float128 abscissa(0.5); // // Evaluate H_9(0.5) using all constexpr arithmetic, and check that it has the expected result: static_assert(h9(abscissa) == 6481);
See constexpr_float_arithmetic_examples.cpp for working code.
Also since the coefficients to the Hermite polynomials are integers, we can
also generate the Hermite coefficients using (fixed precision) cpp_int
s: see constexpr_test_cpp_int_6.cpp.
constexpr
Factorials
We can also generate integer factorials in constexpr_test_cpp_int_5.cpp like so:
template <class T> constexpr T factorial(const T& a) { return a ? a * factorial(a - 1) : 1; }
and validate the result:
constexpr uint1024_t f1 = factorial(uint1024_t(31)); // Factorial 31! static_assert(f1 == 0x1956ad0aae33a4560c5cd2c000000_cppi); // Expected result as an Boost.Multiprecision integer literal.
constexpr
values
Another example in constexpr_test_cpp_int_7.cpp
generates a fresh multiprecision random number each time the file is compiled.
It includes an C++ template implementation of the KISS
random number algorithm by George Marsaglia for cpp_int
integers.
constexpr uint1024_t rand = nth_random_value<uint1024_t>(1000); std::cout << std::hex << rand << std::endl;
See also the random number generation section.