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 areexplicit
, thus syntax such asint 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