Construction, conversion and assignment

Construction, conversion and assignment#

All of mp++’s multiprecision classes default-construct to zero:

int_t n;
assert(n == 0);

rat_t q;
assert(q == 0);

real128 r128;
assert(r128 == 0);

real r;
assert(r == 0);

All of mp++’s multiprecision classes support a uniform style of initialisastion and conversion based on curly brackets. Using the same syntax, it is possible to:

  • initialise multiprecision objects from objects of C++’s numerical types,

  • initialise multiprecision objects from multiprecision objects of a different type,

  • initialise C++ numerical objects from multiprecision objects.

Let’s see a few examples:

int_t n{42};
assert(n == 42);
int m{n};
assert(m == 42);

In the code above, we are creating a multiprecision integer n from the C++ int literal 42. We are then converting n back to int, and checking that the converted value is the original one. As a general rule, mp++ will strive to preserve the exact input value during construction and conversion. If this is not possible, what happens next depends on the types and values involved. For instance, initialising an integer with a floating-point value results in truncation:

int_t n{1.23};
assert(n == 1);

In a similar fashion, initialising an integer with a rational also results in truncation:

int_t n{rat_t{-7, 3}};
assert(n == -2);

integer and rational cannot represent non-finite values, thus construction from such values will raise an exception:

int_t n{std::numeric_limits<double>::infinity()};  // Raises std::domain_error.
rat_t q{std::numeric_limits<double>::quiet_NaN()}; // Raises std::domain_error.

Construction of C++ integrals from integer and rational might fail in case of overflow, and it will produce the truncated value when constructing from rational:

int n{int_t{1} << 1024};         // int construction from very large value,
                                 // raises std::overflow_error.
assert((int{rat_t{4, 3}} == 1)); // int construction from rational truncates.

On the other hand, conversion of integer objects to floating-point C++ types does not raise any error even if it does not preserve the exact value:

float f{int_t{"32327737199221993919239912"}}; // Constructs a single-precision approximation
                                              // of the original integer.

The documentation of the multiprecision classes explains in detail the behaviour during construction and conversion.

Note that, as a general rule, the constructors of mp++’s multiprecision classes are implicit when constructing from numerical types lower in the numerical hierarchy, explicit otherwise. For instance, implicit construction of an integer from a C++ integral value is allowed, but implicit construction from a C++ floating-point value is not:

int_t n1 = 5;   // Valid.
int_t n2 = 1.12 // Will NOT compile.

On the other hand, all the conversion operators in mp++’s multiprecision classes are currently explicit. In particular, converting an mp++ multiprecision object to a fundamental C++ type always requires an explicit cast. This behaviour may be changed in the future so that conversions to fundamental C++ types higher in the hierarchy are implicit (e.g., integer to double conversions).

All of mp++’s multiprecision classes can also be initialised from string-like entities (see the string_type concept for a full list). By default, string input is interpreted as the base-10 representation of the desired value, and parsing follows (hopefully) intuitive rules:

assert(int_t{"-42"} == -42)
assert(rat_t{"3/2"} == 1.5)
assert(real128{"2.5"} == 2.5);
assert((real{"-3.125E-2", 100} == -0.03125));

Note that for real we need to provide the precision as an additional parameter when constructing from string (in this specific example, 100 bits of precision are used). Depending on the multiprecision class, additional string constructors are available which allow to specify a different base for the representation of the value:

assert((int_t{"-101010", 2} == -42))          // Base 2.
assert((rat_t{"2a/1c", 16} == 1.5))           // Base 16.
assert((real{"7B.1", 32, 100} == 235.03125)); // Base 32, 100 bits of precision.

Starting from mp++ 0.19, all multiprecision classes support also initialisastion via user-defined literals, implemented in the mppp::literals inline namespace:

using namespace mppp::literals;

auto n = 123_z1;   // n is an integer with 1 limb of static
                   // storage and initialised with the value 123.
auto q = 456_q2;   // q is a rational with 2 limbs of static
                   // storage and initialised with the value 456.
auto x = 0.1_rq;   // x is a real128 initialised with the
                   // quadruple-precision approximation of
                   // the value 0.1.
auto y = 1.3_r256; // y is a real initialised with the 256-bit
                   // approximation of the value 1.3.

It is of course also possible to assign values to already-constructed multiprecision objects. In general, the behaviour of the assignment operators mirrors the behaviour of the corresponding constructors. For instance:

int_t n{1};
n = 42;
assert(n == 42);
n = -3.7;
assert(n == -3);
n = "-128";
assert(n == -128);
n = std::numeric_limits<double>::quiet_NaN(); // Raises std::domain_error.

rat_t q{3, 4};
q = 1.5;
assert((q == rat_t{3, 2}));
q = int_t{10};
assert(q == 10);
q = "-5/6";
assert((q == rat_t{-5, 6}));
q = std::numeric_limits<double>::infinity();  // Raises std::domain_error.