Literal Types and constexpr Support

| Home | Libraries | People | FAQ | More

### Literal Types and `constexpr` Support

There are two kinds of `constexpr` support in this library:

- The more basic version requires only C++11 and allow the construction of some number types as literals.
- The more advanced support permits constexpr arithmetic and requires at least C++14 constexpr support, and for many operations C++2a support

##### Declaring numeric literals

There are two backend types which are literals:

- float128 (which requires GCC), and
- Instantiations of `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: `number<cpp_int_backend<N,N,signed_magnitude,unchecked,void> >`, where N is chosen to contain just enough digits to hold the number specified.
_cppui | Specifies a value of type: `number<cpp_int_backend<N,N,unsigned_magnitude,unchecked,void> >`, where N is chosen to contain just enough digits to hold the number specified.
_cppiN | Specifies a value of type `number<cpp_int_backend<N,N,signed_magnitude,unchecked,void> >`.
_cppuiN | Specifies a value of type `number<cpp_int_backend<N,N,signed_magnitude,unchecked,void> >`.

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.

##### constexpr arithmetic

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.

###### Calculating Hermite Polynomial coefficients at compile time

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.

###### Random `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.