Integer basics#

Preliminaries#

Let us load the mp++ runtime, include the integer.hpp header and add a couple of using directives to reduce typing:

#pragma cling add_include_path("$CONDA_PREFIX/include")
#pragma cling add_library_path("$CONDA_PREFIX/lib")
#pragma cling load("mp++")

#include <mp++/integer.hpp>

using namespace mppp::literals;
// We will be working with integers with 1
// limb of static storage.
using int_t = mppp::integer<1>;

Let us also include a few useful bits from the standard library:

#include <ios>
#include <iomanip>
#include <iostream>
#include <stdexcept>
#include <initializer_list>
#include <string>
#include <vector>
#include <string_view>

using namespace std::literals;

Construction#

There are many ways to construct multiprecision integers. Default-construction initialises to zero:

{
    int_t n;
    std::cout << "A default-constructed integer is " << n << '\n';
}
A default-constructed integer is 0

We can construct from fundamental C++ types:

int_t{42}
42
int_t{-123.456} // Construction from floating-point types truncates
-123

Implicit construction from C++ integral types is also allowed:

{
    int_t n1 = 42;
    int_t n2 = -123ull;
    std::vector<int_t> v_int = {1, 2, 3};
}

Implicit construction from other types is not allowed (use the direct initialisation syntax instead).

We can construct from string-like types (including char[], std::string and std::string_view):

int_t{"-44939921"}
-44939921
int_t{"77"s}
77
int_t{"-44"sv}
-44

Construction from string representations in bases other than 10 is supported:

int_t{"1001001", 2} // Base 2
73
int_t{"FFFG", 17} // Base 17
78301

mp++ also provides user-defined literals. Here we use the _z1 literal, which constructs integers with 1 limb of static storage from decimal, binary, octal or hexadecimal literals:

-101_z1 // Decimal literal
-101
0b10101010_z1 // Binary literal
170
07117432_z1 // Octal literal
1875738
0xDEADD00D_z1 // Hex literal
3735932941

NOTE: throughout these tutorials, we will almost always use the _z1 literal to construct integer values.

The integer class features also a couple of special constructors, such as a constructor from number of bits:

{
    // n will be constructed with enough
    // storage for a 512bit value.
    int_t n{mppp::integer_bitcnt_t(512)};
    
    std::cout << "The value of n is: " << n << '\n';
    std::cout << "The storage type of n is: " << (n.is_static() ? "static" : "dynamic") << '\n';
}
The value of n is: 0
The storage type of n is: dynamic

And a constructor from an array of limbs:

{
    mp_limb_t arr[] = {123, 456, 789};
    
    int_t n{arr, 3};
    
    std::cout << n << '\n';
}
268482787500620447681014280561276674375803

On a 64-bit architecture, \(n\) is initialised with the value \(123 + 456 \times 2^{64} + 789 \times 2^{128}\). On a 32-bit architecture, \(n\) is initialised with the value \(123 + 456 \times 2^{32} + 789 \times 2^{64}\).

Assignment#

Assignment to multiprecision integers works as expected:

{
    int_t n;
    n = -46_z1;
    std::cout << n << '\n';
}
-46

It is of course possible to assign objects of other types to an integer:

{
    int_t n;
    n = 123;
    std::cout << n << '\n';
}
123
{
    int_t n;
    n = -456.789; // Assignment from floating-point types truncates
    std::cout << n << '\n';
}
-456
{
    int_t n;
    n = "987654"; // Assignment from string assumes base 10
    std::cout << n << '\n';
}
987654

Note however that, due to language limitations, it is not possible to assign a multiprecision integer to a C++ type. As a workaround, explicit casting can be used:

{
    long long n;
    n = static_cast<long long>(42_z1);
    std::cout << n << '\n';
}
42

Conversion#

We can convert multiprecision integers to other types:

double{123_z1} // Explicit type conversion syntax
123.00000
static_cast<long long>(-456_z1) // static_cast syntax
-456

NOTE: all of integer’s conversion operators are explicit, thus syntax such as

int n = 5_z1;

will not work. Use direct initialisation instead:

int n{5_z1};

Or auto (Python-style):

auto n = int{5_z1};

Conversion to C++ integral values will fail in case of overflow:

try {
    static_cast<unsigned>(-1_z1);
} catch (const std::overflow_error &oe) {
    std::cerr << oe.what() << '\n';
}
The conversion of the integer -1 to the type 'unsigned int' results in overflow

If exceptions are to be avoided, we can use the mppp::get() conversion function instead:

{
    std::cout << std::boolalpha;
    
    unsigned out = 42;
    // Attempt to convert -1 to unsigned,
    // storing the result of the conversion
    // in 'out'. mppp::get() will return
    // a boolean exit status.
    const bool res = mppp::get(out, -1_z1);
    
    std::cout << "Did the conversion succeed? " << res << '\n';
    std::cout << "The value of 'out' is still " << out << '\n';
}
Did the conversion succeed? false
The value of 'out' is still 42

Conversion to floating-point types might be subject to rounding:

std::cout << std::setprecision(20) << float{37211191293_z1} << '\n';
37211189248

Integers are contextually convertible to bool, thus you can directly use them in if statements or in conditional operators:

if (1_z1) {
    std::cout << "1 is true\n";
} else {
    std::cout << "1 is false\n";
}
1 is true
std::cout << "123 is " << (123_z1 ? "nonzero" : "zero") << '\n';
123 is nonzero