C++ big unsigned integer class











up vote
4
down vote

favorite
1












Here is a big unsigned integer type I intend to use for learning and "fun" with cryptography.



It uses a std::vector for storing blocks of unsigned integers, the exact type of which can be changed with the template argument block_t. A double_block_t is used internally for the various math operations. This is selected automatically via a traits class (passing it as an individual template argument seemed unnecessary and error prone).



The last block in the vector must never be zero. A zero value is represented by an empty vector.



Some utility functions are omitted; the files below contain the most interesting parts of the code. Here are links to the full repository and a single-file online version.





rsa_math__big_uint.h: The class definition - constructors, utility functions, and operators.



#pragma once

#include "rsa__debug.h"
#include "rsa_math__utils.h"
#include "rsa_math_ops__operations.h"

#include <algorithm>
#include <cstdint>
#include <stdexcept>
#include <type_traits>
#include <vector>

namespace rsa
{

namespace math
{

template<class block_t>
struct block_traits;

template<>
struct block_traits<std::uint8_t> { using double_block_type = std::uint16_t; };

template<>
struct block_traits<std::uint16_t> { using double_block_type = std::uint32_t; };

template<>
struct block_traits<std::uint32_t> { using double_block_type = std::uint64_t; };


template<class block_t>
class big_uint
{
public:

using block_type = block_t;
using double_block_type = typename block_traits<block_type>::double_block_type;
using data_type = std::vector<block_type>;
using block_index_type = std::size_t;
using bit_index_type = std::size_t;

static_assert(utils::is_uint_v<block_type>, "`block_type` must be an unsigned integer.");
static_assert(utils::is_uint_v<double_block_type>, "`double_block_type` must be an unsigned integer.");
static_assert(utils::digits<double_block_type>() == 2 * utils::digits<block_type>(), "`double_block_type` have twice as many digits as `block_type`.");

#pragma region constructors

big_uint();

template<class uint_t>
explicit big_uint(uint_t n);

big_uint(std::initializer_list<block_type> block_values);

explicit big_uint(block_index_type block_count, block_type block_value);

template<class inputit_t>
explicit big_uint(inputit_t first, inputit_t last);

big_uint(big_uint const&) = default;
big_uint(big_uint&&) = default;

#pragma endregion

#pragma region assignment

big_uint& operator=(big_uint const&) = default;
big_uint& operator=(big_uint&&) = default;

template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
big_uint& operator=(uint_t n);

#pragma endregion

#pragma region general

template<class uint_t>
uint_t to_uint() const;

bool is_zero() const;

bool get_bit(bit_index_type i) const;
void set_bit(bit_index_type i, bool value);
void flip_bit(bit_index_type i);

bit_index_type get_most_significant_bit() const;

data_type& data();
data_type const& data() const;

#pragma endregion

#pragma region bitwise operators

big_uint& operator&=(big_uint const& b);

template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
big_uint& operator&=(uint_t n);

big_uint& operator|=(big_uint const& b);

template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
big_uint& operator|=(uint_t n);

big_uint& operator^=(big_uint const& b);

template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
big_uint& operator^=(uint_t n);

big_uint& operator<<=(bit_index_type n);
big_uint& operator>>=(bit_index_type n);

#pragma endregion

#pragma region math operators

big_uint& operator+=(big_uint const& b);

template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
big_uint& operator+=(uint_t n);

big_uint& operator-=(big_uint const& b);

template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
big_uint& operator-=(uint_t n);

big_uint& operator*=(big_uint const& b);

template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
big_uint& operator*=(uint_t n);

big_uint& operator/=(big_uint b);

template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
big_uint& operator/=(uint_t n);

big_uint& operator%=(big_uint b);

template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
big_uint& operator%=(uint_t n);

big_uint& operator++();
big_uint operator++(int);

big_uint& operator--();
big_uint operator--(int);

#pragma endregion

private:

data_type m_data;
};

using big_uint_8 = big_uint<std::uint8_t>;
using big_uint_16 = big_uint<std::uint16_t>;
using big_uint_32 = big_uint<std::uint32_t>;

#pragma region members - construct

template<class block_t>
big_uint<block_t>::big_uint()
{

}

template<class block_t>
template<class uint_t>
big_uint<block_t>::big_uint(uint_t n):
big_uint()
{
static_assert(utils::is_uint_v<uint_t>, "`uint_t` must be an unsigned integer.");

// shifting by >= the number digits in the type is undefined behaviour.
constexpr bool can_rshift = (utils::digits<block_type>() < utils::digits<uint_t>());

while (n != uint_t{ 0 })
{
// integer promotion, conversion to greater rank, implicit conversion to block_type
m_data.push_back(utils::max<block_type>() & n);

if (can_rshift)
n >>= utils::digits<block_type>();
else
n = uint_t{ 0 };
}
}

template<class block_t>
big_uint<block_t>::big_uint(std::initializer_list<block_type> block_values):
m_data(block_values)
{
utils::trim(*this);
}

template<class block_t>
big_uint<block_t>::big_uint(block_index_type block_count, block_type block_value):
m_data(block_count, block_value)
{
utils::trim(*this);
}

template<class block_t>
template<class inputit_t>
big_uint<block_t>::big_uint(inputit_t first, inputit_t last):
m_data(first, last)
{
utils::trim(*this);
}

#pragma endregion

#pragma region members - general

template<class block_t>
template<class uint_t>
uint_t big_uint<block_t>::to_uint() const
{
static_assert(utils::is_uint_v<uint_t>, "`uint_t` must be an unsigned integer.");

// it's much easier to static_assert / throw here if uint_t may be too small.
// checking the actual value would be a lot more work.
{
static_assert(utils::digits<block_t>() <= utils::digits<uint_t>(), "uint_t may be too small to represent this number.");

if (m_data.size() * utils::digits<block_type>() > utils::digits<uint_t>())
throw std::range_error("uint_t may be too small to represent this number.");
}

auto result = uint_t{ 0 };

if (m_data.empty())
return result;

for (auto i = std::size_t{ 0 }; i != m_data.size(); ++i)
result |= (uint_t{ m_data[i] } << (i * utils::digits<block_type>()));

return result;
}

template<class block_t>
bool big_uint<block_t>::is_zero() const
{
return m_data.empty();
}

template<class block_t>
bool big_uint<block_t>::get_bit(bit_index_type i) const
{
auto block_index = i / utils::digits<block_type>();

if (m_data.size() <= block_index)
return false;

auto block_bit = i - (block_index * utils::digits<block_type>());
debug::die_if(block_bit >= utils::digits<block_type>());

return bool((m_data[block_index] >> block_bit) & 1u);
}

template<class block_t>
void big_uint<block_t>::set_bit(bit_index_type i, bool value)
{
auto block_index = i / utils::digits<block_type>();

if (m_data.size() <= block_index)
m_data.resize(block_index + 1u);

auto block_bit = i - (block_index * utils::digits<block_type>());
debug::die_if(block_bit >= utils::digits<block_type>());

auto mask = block_type(block_type{ 1u } << block_bit);
m_data[block_index] |= mask & block_type(block_type{ value } << block_bit);
}

template<class block_t>
void big_uint<block_t>::flip_bit(bit_index_type i)
{
auto block_index = i / utils::digits<block_type>();

if (m_data.size() <= block_index)
m_data.resize(block_index + 1u);

auto block_bit = i - (block_index * utils::digits<block_type>());
debug::die_if(block_bit >= utils::digits<block_type>());

auto mask = block_type(block_type{ 1u } << block_bit);
m_data[block_index] ^= mask;
}

template<class block_t>
typename big_uint<block_t>::bit_index_type big_uint<block_t>::get_most_significant_bit() const
{
if (is_zero())
throw std::logic_error("number must not be zero.");

auto block = m_data.back();
auto count = std::uint8_t{ 0u };

while (block != block_type{ 1u })
{
++count;
block >>= 1u;
}

return bit_index_type{ count + (m_data.size() - 1u) * utils::digits<block_type>() };
}

template<class block_t>
typename big_uint<block_t>::data_type& big_uint<block_t>::data()
{
return m_data;
}

template<class block_t>
typename big_uint<block_t>::data_type const& big_uint<block_t>::data() const
{
return m_data;
}

#pragma endregion

#pragma region members - assignment

template<class block_t>
template<class uint_t, typename>
big_uint<block_t>& big_uint<block_t>::operator=(uint_t n)
{
return (*this = big_uint(n));
}

#pragma endregion

#pragma region members - bitwise operators

template<class block_t>
big_uint<block_t>& big_uint<block_t>::operator&=(big_uint const& b)
{
ops::bit_and_assign(*this, b);
return *this;
}

template<class block_t>
template<class uint_t, typename>
big_uint<block_t>& big_uint<block_t>::operator&=(uint_t n)
{
return operator&=(big_uint(n));
}

template<class block_t>
big_uint<block_t>& big_uint<block_t>::operator|=(big_uint const& b)
{
ops::bit_or_assign(*this, b);
return *this;
}

template<class block_t>
template<class uint_t, typename>
big_uint<block_t>& big_uint<block_t>::operator|=(uint_t n)
{
return operator|=(big_uint(n));
}

template<class block_t>
big_uint<block_t>& big_uint<block_t>::operator^=(big_uint const& b)
{
ops::bit_xor_assign(*this, b);
return *this;
}

template<class block_t>
template<class uint_t, typename>
big_uint<block_t>& big_uint<block_t>::operator^=(uint_t n)
{
return operator^=(big_uint(n));
}

template<class block_t>
big_uint<block_t>& big_uint<block_t>::operator<<=(bit_index_type n)
{
ops::lshift_assign(*this, n);
return *this;
}

template<class block_t>
big_uint<block_t>& big_uint<block_t>::operator>>=(bit_index_type n)
{
ops::rshift_assign(*this, n);
return *this;
}

#pragma endregion

#pragma region members - math operators

template<class block_t>
big_uint<block_t>& big_uint<block_t>::operator+=(big_uint const& b)
{
ops::add_assign(*this, b);
return *this;
}

template<class block_t>
template<class uint_t, typename>
big_uint<block_t>& big_uint<block_t>::operator+=(uint_t n)
{
return operator+=(big_uint(n));
}

template<class block_t>
big_uint<block_t>& big_uint<block_t>::operator-=(big_uint const& b)
{
ops::sub_assign(*this, b);
return *this;
}

template<class block_t>
template<class uint_t, typename>
big_uint<block_t>& big_uint<block_t>::operator-=(uint_t n)
{
return operator-=(big_uint(n));
}

template<class block_t>
big_uint<block_t>& big_uint<block_t>::operator*=(big_uint const& b)
{
ops::mul_assign(*this, b);
return *this;
}

template<class block_t>
template<class uint_t, typename>
big_uint<block_t>& big_uint<block_t>::operator*=(uint_t n)
{
return operator*=(big_uint(n));
}

template<class block_t>
big_uint<block_t>& big_uint<block_t>::operator/=(big_uint b)
{
ops::div_assign(*this, b);
return *this;
}

template<class block_t>
template<class uint_t, typename>
big_uint<block_t>& big_uint<block_t>::operator/=(uint_t n)
{
return operator/=(big_uint(n));
}

template<class block_t>
big_uint<block_t>& big_uint<block_t>::operator%=(big_uint b)
{
ops::mod_assign(*this, b);
return *this;
}

template<class block_t>
template<class uint_t, typename>
big_uint<block_t>& big_uint<block_t>::operator%=(uint_t n)
{
return operator%=(big_uint(n));
}

template<class block_t>
big_uint<block_t>& big_uint<block_t>::operator++()
{
return operator+=(1u);
}

template<class block_t>
big_uint<block_t> big_uint<block_t>::operator++(int)
{
auto temp = *this;
operator++();
return temp;
}

template<class block_t>
big_uint<block_t>& big_uint<block_t>::operator--()
{
return operator-=(1u);
}

template<class block_t>
big_uint<block_t> big_uint<block_t>::operator--(int)
{
auto temp = *this;
operator--();
return temp;
}

#pragma endregion

#pragma region comparison operators

template<class block_t>
bool operator==(big_uint<block_t> const& a, big_uint<block_t> const& b)
{
return std::equal(a.data().begin(), a.data().end(), b.data().begin(), b.data().end());
}

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
bool operator==(big_uint<block_t> const& a, uint_t b)
{
return (a == big_uint<block_t>(b));
}

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
bool operator==(uint_t a, big_uint<block_t> const& b)
{
return (big_uint<block_t>(a) == b);
}

template<class block_t>
bool operator!=(big_uint<block_t> const& a, big_uint<block_t> const& b)
{
return !(a == b);
}

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
bool operator!=(big_uint<block_t> const& a, uint_t b)
{
return (a != big_uint<block_t>(b));
}

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
bool operator!=(uint_t a, big_uint<block_t> const& b)
{
return (big_uint<block_t>(a) != b);
}

template<class block_t>
bool operator<(big_uint<block_t> const& a, big_uint<block_t> const& b)
{
if (a.data().size() < b.data().size())
return true;

if (b.data().size() < a.data().size())
return false;

return std::lexicographical_compare(a.data().rbegin(), a.data().rend(), b.data().rbegin(), b.data().rend());
}

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
bool operator<(big_uint<block_t> const& a, uint_t b)
{
return (a < big_uint<block_t>(b));
}

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
bool operator<(uint_t a, big_uint<block_t> const& b)
{
return (big_uint<block_t>(a) < b);
}

template<class block_t>
bool operator>(big_uint<block_t> const& a, big_uint<block_t> const& b)
{
return (b < a);
}

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
bool operator>(big_uint<block_t> const& a, uint_t b)
{
return (a > big_uint<block_t>(b));
}

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
bool operator>(uint_t a, big_uint<block_t> const& b)
{
return (big_uint<block_t>(a) > b);
}

template<class block_t>
bool operator<=(big_uint<block_t> const& a, big_uint<block_t> const& b)
{
return !(b < a);
}

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
bool operator<=(big_uint<block_t> const& a, uint_t b)
{
return (a <= big_uint<block_t>(b));
}

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
bool operator<=(uint_t a, big_uint<block_t> const& b)
{
return (big_uint<block_t>(a) <= b);
}

template<class block_t>
bool operator>=(big_uint<block_t> const& a, big_uint<block_t> const& b)
{
return !(a < b);
}

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
bool operator>=(big_uint<block_t> const& a, uint_t b)
{
return (a >= big_uint<block_t>(b));
}

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
bool operator>=(uint_t a, big_uint<block_t> const& b)
{
return (big_uint<block_t>(a) >= b);
}

#pragma endregion

#pragma region bitwise operators

template<class block_t>
big_uint<block_t> operator&(big_uint<block_t> a, big_uint<block_t> const& b)
{
return (a &= b);
}

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
big_uint<block_t> operator&(big_uint<block_t> a, uint_t b)
{
return (a &= big_uint<block_t>(b));
}

template<class block_t>
big_uint<block_t> operator|(big_uint<block_t> a, big_uint<block_t> const& b)
{
return (a |= b);
}

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
big_uint<block_t> operator|(big_uint<block_t> a, uint_t b)
{
return (a |= big_uint<block_t>(b));
}

template<class block_t>
big_uint<block_t> operator^(big_uint<block_t> a, big_uint<block_t> const& b)
{
return (a ^= b);
}

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
big_uint<block_t> operator^(big_uint<block_t> a, uint_t b)
{
return (a ^= big_uint<block_t>(b));
}

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
big_uint<block_t> operator<<(big_uint<block_t> a, uint_t b)
{
return (a <<= b);
}

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
big_uint<block_t> operator>>(big_uint<block_t> a, uint_t b)
{
return (a >>= b);
}

#pragma endregion

#pragma region math operators

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
big_uint<block_t> operator+(big_uint<block_t> a, uint_t b)
{
return (a += big_uint<block_t>(b));
}

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
big_uint<block_t> operator+(uint_t a, big_uint<block_t> const& b)
{
return (big_uint<block_t>(a) += b);
}

template<class block_t>
big_uint<block_t> operator+(big_uint<block_t> a, big_uint<block_t> const& b)
{
return (a += b);
}

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
big_uint<block_t> operator-(big_uint<block_t> a, uint_t b)
{
return (a -= big_uint<block_t>(b));
}

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
big_uint<block_t> operator-(uint_t a, big_uint<block_t> const& b)
{
return (big_uint<block_t>(a) -= b);
}

template<class block_t>
big_uint<block_t> operator-(big_uint<block_t> a, big_uint<block_t> const& b)
{
return (a -= b);
}

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
big_uint<block_t> operator*(big_uint<block_t> a, uint_t b)
{
return (a *= big_uint<block_t>(b));
}

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
big_uint<block_t> operator*(uint_t a, big_uint<block_t> const& b)
{
return (big_uint<block_t>(a) *= b);
}

template<class block_t>
big_uint<block_t> operator*(big_uint<block_t> a, big_uint<block_t> const& b)
{
return (a *= b);
}

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
big_uint<block_t> operator/(big_uint<block_t> a, uint_t b)
{
return (a /= big_uint<block_t>(b));
}

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
big_uint<block_t> operator/(uint_t a, big_uint<block_t> const& b)
{
return (big_uint<block_t>(a) /= b);
}

template<class block_t>
big_uint<block_t> operator/(big_uint<block_t> a, big_uint<block_t> const& b)
{
return (a /= b);
}

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
big_uint<block_t> operator%(big_uint<block_t> a, uint_t b)
{
return (a %= big_uint<block_t>(b));
}

template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
big_uint<block_t> operator%(uint_t a, big_uint<block_t> const& b)
{
return (big_uint<block_t>(a) %= b);
}

template<class block_t>
big_uint<block_t> operator%(big_uint<block_t> a, big_uint<block_t> const& b)
{
return (a %= b);
}

#pragma endregion

} // math

} // rsa




rsa_math_ops__operations.h - The core of the various bitwise and math operations. The bitwise operations simply apply the operation to each block (with some fiddling for different sizes of vector). The math operations are implemented with a standard "apply and carry" approach. The division / modulus is based on the Hacker's Delight implementations of Knuth's Algorithm D.



#pragma once

#include "rsa__debug.h"
#include "rsa_math__utils.h"

#include <algorithm>
#include <stdexcept>

namespace rsa
{

namespace math
{

template<class block_t>
class big_uint;

namespace ops
{

template<class block_t>
void bit_and_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
{
const auto min_size = std::min(a.data().size(), b.data().size());

for (auto i = std::size_t{ 0 }; i != min_size; ++i)
a.data()[i] &= b.data()[i];

a.data().resize(min_size);
}

template<class block_t>
void bit_or_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
{
const auto min_size = std::min(a.data().size(), b.data().size());

for (auto i = std::size_t{ 0 }; i != min_size; ++i)
a.data()[i] |= b.data()[i];

std::copy(b.data().begin() + min_size, b.data().end(), std::back_inserter(a.data()));
}

template<class block_t>
void bit_xor_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
{
const auto min_size = std::min(a.data().size(), b.data().size());

for (auto i = std::size_t{ 0 }; i != min_size; ++i)
a.data()[i] ^= b.data()[i];

std::copy(b.data().begin() + min_size, b.data().end(), std::back_inserter(a.data()));

utils::trim(a);
}

template<class block_t>
void lshift_assign(big_uint<block_t>& a, typename big_uint<block_t>::bit_index_type n)
{
using bit_index_t = typename big_uint<block_t>::bit_index_type;
constexpr auto block_digits = utils::digits<block_t>();

if (n == bit_index_t{ 0 })
return;

if (a.is_zero())
return;

// shift by whole blocks
if (n >= block_digits)
{
auto blocks = n / block_digits;
a.data().insert(a.data().begin(), blocks, block_t{ 0 });

n -= (blocks * block_digits);

if (n == bit_index_t{ 0 })
return;
}

debug::die_if(n >= block_digits);
const auto carry_shift = block_t(block_digits - n);
auto carry = block_t{ 0 };

// shift by partial blocks
for (auto& block : a.data())
{
// take high bits, shift them to low bits for next block (cast to fix type from integer promotion)
const auto carry_out = block_t(block >> carry_shift);

// shift low bits to high, apply carry bits
block = (block << n) | carry;

carry = carry_out;
}

if (carry != block_t{ 0 })
a.data().push_back(carry);

debug::die_if(utils::has_extra_empty_blocks(a));
}

template<class block_t>
void rshift_assign(big_uint<block_t>& a, typename big_uint<block_t>::bit_index_type n)
{
using bit_index_t = typename big_uint<block_t>::bit_index_type;
constexpr auto block_digits = utils::digits<block_t>();

if (n == bit_index_t{ 0 })
return;

if (a.is_zero())
return;

// shift by whole blocks
if (n >= block_digits)
{
auto blocks = n / block_digits;
a.data().erase(a.data().begin(), a.data().begin() + std::min<std::size_t>(blocks, a.data().size()));

if (a.is_zero())
return;

n -= (blocks * block_digits);

if (n == bit_index_t{ 0 })
return;
}

debug::die_if(n >= block_digits);
const auto carry_shift = block_t(block_digits - n);
auto carry = block_t{ 0 };

// shift by partial blocks
for (auto i_block = a.data().rbegin(); i_block != a.data().rend(); ++i_block)
{
auto& block = *i_block;

// take low bits, shift them to high bits for the next block (cast to fix type from integer promotion)
const auto carry_out = block_t(block << carry_shift);

// shift high bits to low, apply carry bits
block = (block >> n) | carry;

carry = carry_out;
}

utils::trim(a);
}

template<class block_t>
void add_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
{
using double_block_t = typename big_uint<block_t>::double_block_type;

if (b.is_zero())
return;

if (a.is_zero())
{
a = b;
return;
}

auto& a_data = a.data();
auto const& b_data = b.data();
const auto min_size = std::min(a_data.size(), b_data.size());

auto carry = double_block_t{ 0 };

// both a and b have data
for (auto i = std::size_t{ 0 }; i != min_size; ++i)
{
carry += static_cast<double_block_t>(a_data[i]) + static_cast<double_block_t>(b_data[i]);
a_data[i] = static_cast<block_t>(carry);
carry >>= utils::digits<block_t>();
}

// ran out of data in a, copy over the rest of b
a_data.insert(a_data.end(), b_data.begin() + min_size, b_data.end());

// add carry
for (auto i = min_size; i != a_data.size() && (carry != double_block_t{ 0 }); ++i)
{
carry += static_cast<double_block_t>(a_data[i]);
a_data[i] = static_cast<block_t>(carry);
carry >>= utils::digits<block_t>();
}

// extend a if necessary
if (carry)
a_data.push_back(static_cast<block_t>(carry));
}

template<class block_t>
void sub_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
{
using double_block_t = typename big_uint<block_t>::double_block_type;

if (b.is_zero())
return;

if (b > a)
throw std::invalid_argument("cannot subtract larger value from smaller one.");

debug::die_if(a.data().size() < b.data().size());

auto& a_data = a.data();
auto const& b_data = b.data();

auto borrow = double_block_t{ 0 };

// both a and b have data
for (auto i = std::size_t{ 0 }; i != b_data.size(); ++i)
{
borrow = static_cast<double_block_t>(a_data[i]) - static_cast<double_block_t>(b_data[i]) - borrow;
a_data[i] = static_cast<block_t>(borrow);
borrow = (borrow >> utils::digits<block_t>()) & double_block_t { 1 };
}

// ran out of data in b, subtract borrow
for (auto i = b_data.size(); i != a_data.size() && (borrow != double_block_t{ 0 }); ++i)
{
borrow = static_cast<double_block_t>(a_data[i]) - borrow;
a_data[i] = static_cast<block_t>(borrow);
borrow = (borrow >> utils::digits<block_t>()) & double_block_t { 1 };
}

utils::trim(a);
}

template<class block_t>
void mul_assign(big_uint<block_t>& lhs, big_uint<block_t> const& rhs)
{
using double_block_t = typename big_uint<block_t>::double_block_type;

if (lhs.is_zero()) return;
if (rhs.is_zero()) { lhs.data().clear(); return; }

if (rhs == 1u) return;
if (lhs == 1u) { lhs = rhs; return; }

// note: long multiplication relies on:
// double_block_t holding (max<block_t>() * max<block_t>() + 2 * max<block_t>())
// which is exactly the case if digits<double_block_t>() == 2 * digits<block_t>();

{
auto b = rhs; // TODO: we only need to copy this if lhs and rhs refer to the same object
auto a = std::move(lhs);
auto& c = lhs;

auto const& a_data = a.data();
auto const& b_data = b.data();
auto& c_data = c.data();

c_data.resize(a_data.size() + b_data.size());

for (auto i = std::size_t{ 0 }; i != a_data.size(); ++i)
{
auto carry = double_block_t{ 0 };

for (auto j = std::size_t{ 0 }; j != b_data.size(); ++j)
{
carry += static_cast<double_block_t>(a_data[i]) * static_cast<double_block_t>(b_data[j]);
carry += c_data[i + j];
c_data[i + j] = static_cast<block_t>(carry);
carry >>= utils::digits<block_t>();
}

// c_data[i + b_data.size()] is always zero
if (carry)
c_data[i + b_data.size()] = static_cast<block_t>(carry);
}

utils::trim(c);
}
}

template<class block_t>
void divmod(big_uint<block_t>& quotient, big_uint<block_t>& remainder, big_uint<block_t> dividend, big_uint<block_t> divisor)
{
using double_block_t = typename big_uint<block_t>::double_block_type;

quotient.data().clear();
remainder.data().clear();

debug::die_if(divisor.is_zero());
debug::die_if(dividend < divisor);
debug::die_if(dividend == divisor);
debug::die_if(dividend.data().size() == 1u && divisor.data().size() == 1u);

auto const get_num_leading_zeros = (block_t x)
{
debug::die_if(x == 0);

auto count = std::size_t{ 0 };

while (x != 0)
{
++count;
x >>= 1;
}

return utils::digits<block_t>() - count;
};

auto const promote = (double_block_t b) { return b << utils::digits<block_t>(); };
auto const demote = (double_block_t b) { return b >> utils::digits<block_t>(); };
auto const checked_sub = (block_t& out, block_t a, block_t b) { return ((out = a - b) > a); };

{
auto& d = divisor;
auto& n = remainder;
remainder = std::move(dividend);
auto& q = quotient;

q.data().resize(n.data().size() - d.data().size() + 1);

// single digit divisor
if (d.data().size() == 1)
{
auto k = double_block_t{ 0 };
auto const v = d.data()[0];

for (auto i = n.data().size(); i != 0; --i)
{
auto const index = i - 1;
k = (k << utils::digits<block_t>()) + n.data()[index];
q.data()[index] = static_cast<block_t>(k / v);
k -= static_cast<double_block_t>(q.data()[index]) * v;
}

n.data().clear();

if (k != 0)
n.data().push_back(static_cast<block_t>(k));

utils::trim(q);

return;
}

auto const b = double_block_t{ 1 } << utils::digits<block_t>();
auto const ns = n.data().size(); // m
auto const ds = d.data().size(); // n

auto shift = get_num_leading_zeros(d.data().back());
d <<= shift;
n <<= shift;

if (n.data().size() == ns)
n.data().push_back(block_t{ 0 });

for (auto i_outer = (ns - ds) + 1; i_outer != 0; --i_outer)
{
auto const j = i_outer - 1;

// take the top two blocks of n, divide by top block of d, calc remainder
auto v = d.data()[ds - 1];
auto n_block = static_cast<double_block_t>(promote(n.data()[j + ds]) | n.data()[j + ds - 1]);
auto qhat = static_cast<double_block_t>(n_block / v);
auto rhat = static_cast<double_block_t>(n_block - qhat * v);

// q is too big or (looking at next block) remainder is smaller than what will be taken away
while (qhat >= b || (qhat * d.data()[ds - 2]) > (promote(rhat) + n.data()[j + ds - 2]))
{
qhat -= 1; rhat += v;
if (rhat >= b) break;
}

// qhat is now correct, or 1 too high (extremely rare)

// multiply divisor by qhat and subtract from n
auto underflow = false;
{
auto k = double_block_t{ 0 };
for (auto i = std::size_t{ 0 }; i != ds; ++i)
{
auto const p = static_cast<double_block_t>(qhat * d.data()[i]);
auto const t = static_cast<double_block_t>(n.data()[i + j] - k - static_cast<block_t>(p));
n.data()[i + j] = static_cast<block_t>(t);
k = static_cast<double_block_t>(demote(p) - (static_cast<std::make_signed_t<double_block_t>>(t) >> utils::digits<block_t>()));
}

if (k != 0)
underflow |= checked_sub(n.data()[j + ds], n.data()[j + ds], static_cast<block_t>(k));
}

// set quotient
q.data()[j] = static_cast<block_t>(qhat);

// underflow! (qhat was 1 too high)
// decrement q and add back one divisor to the remainder
if (underflow)
{
q.data()[j] = q.data()[j] - 1;

auto k = double_block_t{ 0 };
for (auto i = std::size_t{ 0 }; i != ds; ++i)
{
auto const t = double_block_t{ n.data()[i + j] } + d.data()[i] + k;
n.data()[i + j] = static_cast<block_t>(t);
k = static_cast<double_block_t>(t >> utils::digits<block_t>());
}

n.data()[j + ds] += static_cast<block_t>(k);
}
}

utils::trim(q);

// shift remainder back
utils::trim(n);
n >>= shift;
}
}

template<class block_t>
void div_assign(big_uint<block_t>& lhs, big_uint<block_t> const& rhs)
{
using double_block_t = typename big_uint<block_t>::double_block_type;

if (rhs.is_zero())
throw std::invalid_argument("divisor cannot be zero.");

if (lhs < rhs) { lhs.data().clear(); return; }
if (lhs == rhs) { lhs.data().clear(); lhs.data().push_back(1); return; }

if (lhs.data().size() == 1u && rhs.data().size() == 1u)
{
lhs = static_cast<block_t>(lhs.data()[0] / rhs.data()[0]);
return;
}

{
auto q = big_uint<block_t>();
auto r = big_uint<block_t>();
divmod(q, r, lhs, rhs);
lhs = std::move(q);
}
}

template<class block_t>
void mod_assign(big_uint<block_t>& lhs, big_uint<block_t> const& rhs)
{
using double_block_t = typename big_uint<block_t>::double_block_type;

if (rhs.is_zero())
throw std::invalid_argument("divisor cannot be zero.");

if (lhs < rhs) { return; }
if (lhs == rhs) { lhs.data().clear(); return; }

if (lhs.data().size() == 1u && rhs.data().size() == 1u)
{
lhs = static_cast<block_t>(lhs.data()[0] % rhs.data()[0]);
utils::trim(lhs);
return;
}

{
auto q = big_uint<block_t>();
auto r = big_uint<block_t>();
divmod(q, r, lhs, rhs);
lhs = std::move(r);
}
}

// utility for testing (when we need both quotient and remainder)
template<class block_t>
void divmod_test(big_uint<block_t>& quotient, big_uint<block_t>& remainder, big_uint<block_t> const& dividend, big_uint<block_t> const& divisor)
{
quotient.data().clear();
remainder.data().clear();

if (divisor.is_zero())
throw std::invalid_argument("divisor cannot be zero.");

if (dividend < divisor) { remainder = dividend; return; }
if (dividend == divisor) { quotient.data().push_back(1); return; }

if (dividend.data().size() == 1u && divisor.data().size() == 1u)
{
quotient = static_cast<block_t>(dividend.data()[0] / divisor.data()[0]);
remainder = static_cast<block_t>(dividend.data()[0] % divisor.data()[0]);

utils::trim(remainder);
return;
}

divmod(quotient, remainder, dividend, divisor);
}

} // ops

} // math

} // rsa




rsa_math__utils.h



#pragma once

#include <algorithm>
#include <cstdint>
#include <limits>
#include <type_traits>

namespace rsa
{

namespace math
{

template<class block_t>
class big_uint;

namespace utils
{

template<class uint_t>
constexpr bool is_uint_v = (std::is_integral_v<uint_t> && std::is_unsigned_v<uint_t> && !std::is_same_v<uint_t, bool>);

template<class uint_t>
using enable_if_uint_t = std::enable_if_t<is_uint_v<uint_t>>;


template<class t>
constexpr std::uint32_t digits()
{
return std::uint32_t(std::numeric_limits<t>::digits);
}

template<class t>
constexpr t max()
{
return std::numeric_limits<t>::max();
}


template<class block_t>
bool has_extra_empty_blocks(big_uint<block_t> const& a)
{
return
(std::find_if(a.data().rbegin(), a.data().rend(),
(block_t b) { return b != block_t{ 0 }; }).base() !=
a.data().end());
}

template<class block_t>
void trim(big_uint<block_t>& a)
{
a.data().erase(
std::find_if(a.data().rbegin(), a.data().rend(),
(block_t b) { return b != block_t{ 0 }; }).base(),
a.data().end());
}

} // utils

} // math

} // rsa




Any advice / feedback is welcome.



Any tips for neatening the math::ops functions are especially appreciated. The divmod function is quite fiddly and contains a lot of casts, several of which are only necessary for smaller block sizes (i.e. std::uint8_ts and integer promotion don't mix well).



(Templating the class on block size seemed like a good idea at the time, but now it just looks overcomplicated, so I'll probably just fix the block size to std::uint32_t in the class with a typedef.)



Is there anything missing / dodgy looking / surprising?





Suggested usage is just like a normal numeric type. (Note, however, that the various math operators require unsigned types, and don't work with signed ones).



#include <limits>
#include <iostream>

int main()
{
auto a = rsa::math::big_uint_32(std::numeric_limits<std::uint64_t>::max());
auto b = (a * a) + 2u * a;

std::cout << std::hex;
for (auto block : b.data())
std::cout << block << " ";
std::cout << std::endl;
}




Full repository.



Online compiler.










share|improve this question




























    up vote
    4
    down vote

    favorite
    1












    Here is a big unsigned integer type I intend to use for learning and "fun" with cryptography.



    It uses a std::vector for storing blocks of unsigned integers, the exact type of which can be changed with the template argument block_t. A double_block_t is used internally for the various math operations. This is selected automatically via a traits class (passing it as an individual template argument seemed unnecessary and error prone).



    The last block in the vector must never be zero. A zero value is represented by an empty vector.



    Some utility functions are omitted; the files below contain the most interesting parts of the code. Here are links to the full repository and a single-file online version.





    rsa_math__big_uint.h: The class definition - constructors, utility functions, and operators.



    #pragma once

    #include "rsa__debug.h"
    #include "rsa_math__utils.h"
    #include "rsa_math_ops__operations.h"

    #include <algorithm>
    #include <cstdint>
    #include <stdexcept>
    #include <type_traits>
    #include <vector>

    namespace rsa
    {

    namespace math
    {

    template<class block_t>
    struct block_traits;

    template<>
    struct block_traits<std::uint8_t> { using double_block_type = std::uint16_t; };

    template<>
    struct block_traits<std::uint16_t> { using double_block_type = std::uint32_t; };

    template<>
    struct block_traits<std::uint32_t> { using double_block_type = std::uint64_t; };


    template<class block_t>
    class big_uint
    {
    public:

    using block_type = block_t;
    using double_block_type = typename block_traits<block_type>::double_block_type;
    using data_type = std::vector<block_type>;
    using block_index_type = std::size_t;
    using bit_index_type = std::size_t;

    static_assert(utils::is_uint_v<block_type>, "`block_type` must be an unsigned integer.");
    static_assert(utils::is_uint_v<double_block_type>, "`double_block_type` must be an unsigned integer.");
    static_assert(utils::digits<double_block_type>() == 2 * utils::digits<block_type>(), "`double_block_type` have twice as many digits as `block_type`.");

    #pragma region constructors

    big_uint();

    template<class uint_t>
    explicit big_uint(uint_t n);

    big_uint(std::initializer_list<block_type> block_values);

    explicit big_uint(block_index_type block_count, block_type block_value);

    template<class inputit_t>
    explicit big_uint(inputit_t first, inputit_t last);

    big_uint(big_uint const&) = default;
    big_uint(big_uint&&) = default;

    #pragma endregion

    #pragma region assignment

    big_uint& operator=(big_uint const&) = default;
    big_uint& operator=(big_uint&&) = default;

    template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    big_uint& operator=(uint_t n);

    #pragma endregion

    #pragma region general

    template<class uint_t>
    uint_t to_uint() const;

    bool is_zero() const;

    bool get_bit(bit_index_type i) const;
    void set_bit(bit_index_type i, bool value);
    void flip_bit(bit_index_type i);

    bit_index_type get_most_significant_bit() const;

    data_type& data();
    data_type const& data() const;

    #pragma endregion

    #pragma region bitwise operators

    big_uint& operator&=(big_uint const& b);

    template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    big_uint& operator&=(uint_t n);

    big_uint& operator|=(big_uint const& b);

    template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    big_uint& operator|=(uint_t n);

    big_uint& operator^=(big_uint const& b);

    template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    big_uint& operator^=(uint_t n);

    big_uint& operator<<=(bit_index_type n);
    big_uint& operator>>=(bit_index_type n);

    #pragma endregion

    #pragma region math operators

    big_uint& operator+=(big_uint const& b);

    template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    big_uint& operator+=(uint_t n);

    big_uint& operator-=(big_uint const& b);

    template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    big_uint& operator-=(uint_t n);

    big_uint& operator*=(big_uint const& b);

    template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    big_uint& operator*=(uint_t n);

    big_uint& operator/=(big_uint b);

    template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    big_uint& operator/=(uint_t n);

    big_uint& operator%=(big_uint b);

    template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    big_uint& operator%=(uint_t n);

    big_uint& operator++();
    big_uint operator++(int);

    big_uint& operator--();
    big_uint operator--(int);

    #pragma endregion

    private:

    data_type m_data;
    };

    using big_uint_8 = big_uint<std::uint8_t>;
    using big_uint_16 = big_uint<std::uint16_t>;
    using big_uint_32 = big_uint<std::uint32_t>;

    #pragma region members - construct

    template<class block_t>
    big_uint<block_t>::big_uint()
    {

    }

    template<class block_t>
    template<class uint_t>
    big_uint<block_t>::big_uint(uint_t n):
    big_uint()
    {
    static_assert(utils::is_uint_v<uint_t>, "`uint_t` must be an unsigned integer.");

    // shifting by >= the number digits in the type is undefined behaviour.
    constexpr bool can_rshift = (utils::digits<block_type>() < utils::digits<uint_t>());

    while (n != uint_t{ 0 })
    {
    // integer promotion, conversion to greater rank, implicit conversion to block_type
    m_data.push_back(utils::max<block_type>() & n);

    if (can_rshift)
    n >>= utils::digits<block_type>();
    else
    n = uint_t{ 0 };
    }
    }

    template<class block_t>
    big_uint<block_t>::big_uint(std::initializer_list<block_type> block_values):
    m_data(block_values)
    {
    utils::trim(*this);
    }

    template<class block_t>
    big_uint<block_t>::big_uint(block_index_type block_count, block_type block_value):
    m_data(block_count, block_value)
    {
    utils::trim(*this);
    }

    template<class block_t>
    template<class inputit_t>
    big_uint<block_t>::big_uint(inputit_t first, inputit_t last):
    m_data(first, last)
    {
    utils::trim(*this);
    }

    #pragma endregion

    #pragma region members - general

    template<class block_t>
    template<class uint_t>
    uint_t big_uint<block_t>::to_uint() const
    {
    static_assert(utils::is_uint_v<uint_t>, "`uint_t` must be an unsigned integer.");

    // it's much easier to static_assert / throw here if uint_t may be too small.
    // checking the actual value would be a lot more work.
    {
    static_assert(utils::digits<block_t>() <= utils::digits<uint_t>(), "uint_t may be too small to represent this number.");

    if (m_data.size() * utils::digits<block_type>() > utils::digits<uint_t>())
    throw std::range_error("uint_t may be too small to represent this number.");
    }

    auto result = uint_t{ 0 };

    if (m_data.empty())
    return result;

    for (auto i = std::size_t{ 0 }; i != m_data.size(); ++i)
    result |= (uint_t{ m_data[i] } << (i * utils::digits<block_type>()));

    return result;
    }

    template<class block_t>
    bool big_uint<block_t>::is_zero() const
    {
    return m_data.empty();
    }

    template<class block_t>
    bool big_uint<block_t>::get_bit(bit_index_type i) const
    {
    auto block_index = i / utils::digits<block_type>();

    if (m_data.size() <= block_index)
    return false;

    auto block_bit = i - (block_index * utils::digits<block_type>());
    debug::die_if(block_bit >= utils::digits<block_type>());

    return bool((m_data[block_index] >> block_bit) & 1u);
    }

    template<class block_t>
    void big_uint<block_t>::set_bit(bit_index_type i, bool value)
    {
    auto block_index = i / utils::digits<block_type>();

    if (m_data.size() <= block_index)
    m_data.resize(block_index + 1u);

    auto block_bit = i - (block_index * utils::digits<block_type>());
    debug::die_if(block_bit >= utils::digits<block_type>());

    auto mask = block_type(block_type{ 1u } << block_bit);
    m_data[block_index] |= mask & block_type(block_type{ value } << block_bit);
    }

    template<class block_t>
    void big_uint<block_t>::flip_bit(bit_index_type i)
    {
    auto block_index = i / utils::digits<block_type>();

    if (m_data.size() <= block_index)
    m_data.resize(block_index + 1u);

    auto block_bit = i - (block_index * utils::digits<block_type>());
    debug::die_if(block_bit >= utils::digits<block_type>());

    auto mask = block_type(block_type{ 1u } << block_bit);
    m_data[block_index] ^= mask;
    }

    template<class block_t>
    typename big_uint<block_t>::bit_index_type big_uint<block_t>::get_most_significant_bit() const
    {
    if (is_zero())
    throw std::logic_error("number must not be zero.");

    auto block = m_data.back();
    auto count = std::uint8_t{ 0u };

    while (block != block_type{ 1u })
    {
    ++count;
    block >>= 1u;
    }

    return bit_index_type{ count + (m_data.size() - 1u) * utils::digits<block_type>() };
    }

    template<class block_t>
    typename big_uint<block_t>::data_type& big_uint<block_t>::data()
    {
    return m_data;
    }

    template<class block_t>
    typename big_uint<block_t>::data_type const& big_uint<block_t>::data() const
    {
    return m_data;
    }

    #pragma endregion

    #pragma region members - assignment

    template<class block_t>
    template<class uint_t, typename>
    big_uint<block_t>& big_uint<block_t>::operator=(uint_t n)
    {
    return (*this = big_uint(n));
    }

    #pragma endregion

    #pragma region members - bitwise operators

    template<class block_t>
    big_uint<block_t>& big_uint<block_t>::operator&=(big_uint const& b)
    {
    ops::bit_and_assign(*this, b);
    return *this;
    }

    template<class block_t>
    template<class uint_t, typename>
    big_uint<block_t>& big_uint<block_t>::operator&=(uint_t n)
    {
    return operator&=(big_uint(n));
    }

    template<class block_t>
    big_uint<block_t>& big_uint<block_t>::operator|=(big_uint const& b)
    {
    ops::bit_or_assign(*this, b);
    return *this;
    }

    template<class block_t>
    template<class uint_t, typename>
    big_uint<block_t>& big_uint<block_t>::operator|=(uint_t n)
    {
    return operator|=(big_uint(n));
    }

    template<class block_t>
    big_uint<block_t>& big_uint<block_t>::operator^=(big_uint const& b)
    {
    ops::bit_xor_assign(*this, b);
    return *this;
    }

    template<class block_t>
    template<class uint_t, typename>
    big_uint<block_t>& big_uint<block_t>::operator^=(uint_t n)
    {
    return operator^=(big_uint(n));
    }

    template<class block_t>
    big_uint<block_t>& big_uint<block_t>::operator<<=(bit_index_type n)
    {
    ops::lshift_assign(*this, n);
    return *this;
    }

    template<class block_t>
    big_uint<block_t>& big_uint<block_t>::operator>>=(bit_index_type n)
    {
    ops::rshift_assign(*this, n);
    return *this;
    }

    #pragma endregion

    #pragma region members - math operators

    template<class block_t>
    big_uint<block_t>& big_uint<block_t>::operator+=(big_uint const& b)
    {
    ops::add_assign(*this, b);
    return *this;
    }

    template<class block_t>
    template<class uint_t, typename>
    big_uint<block_t>& big_uint<block_t>::operator+=(uint_t n)
    {
    return operator+=(big_uint(n));
    }

    template<class block_t>
    big_uint<block_t>& big_uint<block_t>::operator-=(big_uint const& b)
    {
    ops::sub_assign(*this, b);
    return *this;
    }

    template<class block_t>
    template<class uint_t, typename>
    big_uint<block_t>& big_uint<block_t>::operator-=(uint_t n)
    {
    return operator-=(big_uint(n));
    }

    template<class block_t>
    big_uint<block_t>& big_uint<block_t>::operator*=(big_uint const& b)
    {
    ops::mul_assign(*this, b);
    return *this;
    }

    template<class block_t>
    template<class uint_t, typename>
    big_uint<block_t>& big_uint<block_t>::operator*=(uint_t n)
    {
    return operator*=(big_uint(n));
    }

    template<class block_t>
    big_uint<block_t>& big_uint<block_t>::operator/=(big_uint b)
    {
    ops::div_assign(*this, b);
    return *this;
    }

    template<class block_t>
    template<class uint_t, typename>
    big_uint<block_t>& big_uint<block_t>::operator/=(uint_t n)
    {
    return operator/=(big_uint(n));
    }

    template<class block_t>
    big_uint<block_t>& big_uint<block_t>::operator%=(big_uint b)
    {
    ops::mod_assign(*this, b);
    return *this;
    }

    template<class block_t>
    template<class uint_t, typename>
    big_uint<block_t>& big_uint<block_t>::operator%=(uint_t n)
    {
    return operator%=(big_uint(n));
    }

    template<class block_t>
    big_uint<block_t>& big_uint<block_t>::operator++()
    {
    return operator+=(1u);
    }

    template<class block_t>
    big_uint<block_t> big_uint<block_t>::operator++(int)
    {
    auto temp = *this;
    operator++();
    return temp;
    }

    template<class block_t>
    big_uint<block_t>& big_uint<block_t>::operator--()
    {
    return operator-=(1u);
    }

    template<class block_t>
    big_uint<block_t> big_uint<block_t>::operator--(int)
    {
    auto temp = *this;
    operator--();
    return temp;
    }

    #pragma endregion

    #pragma region comparison operators

    template<class block_t>
    bool operator==(big_uint<block_t> const& a, big_uint<block_t> const& b)
    {
    return std::equal(a.data().begin(), a.data().end(), b.data().begin(), b.data().end());
    }

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    bool operator==(big_uint<block_t> const& a, uint_t b)
    {
    return (a == big_uint<block_t>(b));
    }

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    bool operator==(uint_t a, big_uint<block_t> const& b)
    {
    return (big_uint<block_t>(a) == b);
    }

    template<class block_t>
    bool operator!=(big_uint<block_t> const& a, big_uint<block_t> const& b)
    {
    return !(a == b);
    }

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    bool operator!=(big_uint<block_t> const& a, uint_t b)
    {
    return (a != big_uint<block_t>(b));
    }

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    bool operator!=(uint_t a, big_uint<block_t> const& b)
    {
    return (big_uint<block_t>(a) != b);
    }

    template<class block_t>
    bool operator<(big_uint<block_t> const& a, big_uint<block_t> const& b)
    {
    if (a.data().size() < b.data().size())
    return true;

    if (b.data().size() < a.data().size())
    return false;

    return std::lexicographical_compare(a.data().rbegin(), a.data().rend(), b.data().rbegin(), b.data().rend());
    }

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    bool operator<(big_uint<block_t> const& a, uint_t b)
    {
    return (a < big_uint<block_t>(b));
    }

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    bool operator<(uint_t a, big_uint<block_t> const& b)
    {
    return (big_uint<block_t>(a) < b);
    }

    template<class block_t>
    bool operator>(big_uint<block_t> const& a, big_uint<block_t> const& b)
    {
    return (b < a);
    }

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    bool operator>(big_uint<block_t> const& a, uint_t b)
    {
    return (a > big_uint<block_t>(b));
    }

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    bool operator>(uint_t a, big_uint<block_t> const& b)
    {
    return (big_uint<block_t>(a) > b);
    }

    template<class block_t>
    bool operator<=(big_uint<block_t> const& a, big_uint<block_t> const& b)
    {
    return !(b < a);
    }

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    bool operator<=(big_uint<block_t> const& a, uint_t b)
    {
    return (a <= big_uint<block_t>(b));
    }

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    bool operator<=(uint_t a, big_uint<block_t> const& b)
    {
    return (big_uint<block_t>(a) <= b);
    }

    template<class block_t>
    bool operator>=(big_uint<block_t> const& a, big_uint<block_t> const& b)
    {
    return !(a < b);
    }

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    bool operator>=(big_uint<block_t> const& a, uint_t b)
    {
    return (a >= big_uint<block_t>(b));
    }

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    bool operator>=(uint_t a, big_uint<block_t> const& b)
    {
    return (big_uint<block_t>(a) >= b);
    }

    #pragma endregion

    #pragma region bitwise operators

    template<class block_t>
    big_uint<block_t> operator&(big_uint<block_t> a, big_uint<block_t> const& b)
    {
    return (a &= b);
    }

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    big_uint<block_t> operator&(big_uint<block_t> a, uint_t b)
    {
    return (a &= big_uint<block_t>(b));
    }

    template<class block_t>
    big_uint<block_t> operator|(big_uint<block_t> a, big_uint<block_t> const& b)
    {
    return (a |= b);
    }

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    big_uint<block_t> operator|(big_uint<block_t> a, uint_t b)
    {
    return (a |= big_uint<block_t>(b));
    }

    template<class block_t>
    big_uint<block_t> operator^(big_uint<block_t> a, big_uint<block_t> const& b)
    {
    return (a ^= b);
    }

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    big_uint<block_t> operator^(big_uint<block_t> a, uint_t b)
    {
    return (a ^= big_uint<block_t>(b));
    }

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    big_uint<block_t> operator<<(big_uint<block_t> a, uint_t b)
    {
    return (a <<= b);
    }

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    big_uint<block_t> operator>>(big_uint<block_t> a, uint_t b)
    {
    return (a >>= b);
    }

    #pragma endregion

    #pragma region math operators

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    big_uint<block_t> operator+(big_uint<block_t> a, uint_t b)
    {
    return (a += big_uint<block_t>(b));
    }

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    big_uint<block_t> operator+(uint_t a, big_uint<block_t> const& b)
    {
    return (big_uint<block_t>(a) += b);
    }

    template<class block_t>
    big_uint<block_t> operator+(big_uint<block_t> a, big_uint<block_t> const& b)
    {
    return (a += b);
    }

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    big_uint<block_t> operator-(big_uint<block_t> a, uint_t b)
    {
    return (a -= big_uint<block_t>(b));
    }

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    big_uint<block_t> operator-(uint_t a, big_uint<block_t> const& b)
    {
    return (big_uint<block_t>(a) -= b);
    }

    template<class block_t>
    big_uint<block_t> operator-(big_uint<block_t> a, big_uint<block_t> const& b)
    {
    return (a -= b);
    }

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    big_uint<block_t> operator*(big_uint<block_t> a, uint_t b)
    {
    return (a *= big_uint<block_t>(b));
    }

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    big_uint<block_t> operator*(uint_t a, big_uint<block_t> const& b)
    {
    return (big_uint<block_t>(a) *= b);
    }

    template<class block_t>
    big_uint<block_t> operator*(big_uint<block_t> a, big_uint<block_t> const& b)
    {
    return (a *= b);
    }

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    big_uint<block_t> operator/(big_uint<block_t> a, uint_t b)
    {
    return (a /= big_uint<block_t>(b));
    }

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    big_uint<block_t> operator/(uint_t a, big_uint<block_t> const& b)
    {
    return (big_uint<block_t>(a) /= b);
    }

    template<class block_t>
    big_uint<block_t> operator/(big_uint<block_t> a, big_uint<block_t> const& b)
    {
    return (a /= b);
    }

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    big_uint<block_t> operator%(big_uint<block_t> a, uint_t b)
    {
    return (a %= big_uint<block_t>(b));
    }

    template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
    big_uint<block_t> operator%(uint_t a, big_uint<block_t> const& b)
    {
    return (big_uint<block_t>(a) %= b);
    }

    template<class block_t>
    big_uint<block_t> operator%(big_uint<block_t> a, big_uint<block_t> const& b)
    {
    return (a %= b);
    }

    #pragma endregion

    } // math

    } // rsa




    rsa_math_ops__operations.h - The core of the various bitwise and math operations. The bitwise operations simply apply the operation to each block (with some fiddling for different sizes of vector). The math operations are implemented with a standard "apply and carry" approach. The division / modulus is based on the Hacker's Delight implementations of Knuth's Algorithm D.



    #pragma once

    #include "rsa__debug.h"
    #include "rsa_math__utils.h"

    #include <algorithm>
    #include <stdexcept>

    namespace rsa
    {

    namespace math
    {

    template<class block_t>
    class big_uint;

    namespace ops
    {

    template<class block_t>
    void bit_and_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
    {
    const auto min_size = std::min(a.data().size(), b.data().size());

    for (auto i = std::size_t{ 0 }; i != min_size; ++i)
    a.data()[i] &= b.data()[i];

    a.data().resize(min_size);
    }

    template<class block_t>
    void bit_or_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
    {
    const auto min_size = std::min(a.data().size(), b.data().size());

    for (auto i = std::size_t{ 0 }; i != min_size; ++i)
    a.data()[i] |= b.data()[i];

    std::copy(b.data().begin() + min_size, b.data().end(), std::back_inserter(a.data()));
    }

    template<class block_t>
    void bit_xor_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
    {
    const auto min_size = std::min(a.data().size(), b.data().size());

    for (auto i = std::size_t{ 0 }; i != min_size; ++i)
    a.data()[i] ^= b.data()[i];

    std::copy(b.data().begin() + min_size, b.data().end(), std::back_inserter(a.data()));

    utils::trim(a);
    }

    template<class block_t>
    void lshift_assign(big_uint<block_t>& a, typename big_uint<block_t>::bit_index_type n)
    {
    using bit_index_t = typename big_uint<block_t>::bit_index_type;
    constexpr auto block_digits = utils::digits<block_t>();

    if (n == bit_index_t{ 0 })
    return;

    if (a.is_zero())
    return;

    // shift by whole blocks
    if (n >= block_digits)
    {
    auto blocks = n / block_digits;
    a.data().insert(a.data().begin(), blocks, block_t{ 0 });

    n -= (blocks * block_digits);

    if (n == bit_index_t{ 0 })
    return;
    }

    debug::die_if(n >= block_digits);
    const auto carry_shift = block_t(block_digits - n);
    auto carry = block_t{ 0 };

    // shift by partial blocks
    for (auto& block : a.data())
    {
    // take high bits, shift them to low bits for next block (cast to fix type from integer promotion)
    const auto carry_out = block_t(block >> carry_shift);

    // shift low bits to high, apply carry bits
    block = (block << n) | carry;

    carry = carry_out;
    }

    if (carry != block_t{ 0 })
    a.data().push_back(carry);

    debug::die_if(utils::has_extra_empty_blocks(a));
    }

    template<class block_t>
    void rshift_assign(big_uint<block_t>& a, typename big_uint<block_t>::bit_index_type n)
    {
    using bit_index_t = typename big_uint<block_t>::bit_index_type;
    constexpr auto block_digits = utils::digits<block_t>();

    if (n == bit_index_t{ 0 })
    return;

    if (a.is_zero())
    return;

    // shift by whole blocks
    if (n >= block_digits)
    {
    auto blocks = n / block_digits;
    a.data().erase(a.data().begin(), a.data().begin() + std::min<std::size_t>(blocks, a.data().size()));

    if (a.is_zero())
    return;

    n -= (blocks * block_digits);

    if (n == bit_index_t{ 0 })
    return;
    }

    debug::die_if(n >= block_digits);
    const auto carry_shift = block_t(block_digits - n);
    auto carry = block_t{ 0 };

    // shift by partial blocks
    for (auto i_block = a.data().rbegin(); i_block != a.data().rend(); ++i_block)
    {
    auto& block = *i_block;

    // take low bits, shift them to high bits for the next block (cast to fix type from integer promotion)
    const auto carry_out = block_t(block << carry_shift);

    // shift high bits to low, apply carry bits
    block = (block >> n) | carry;

    carry = carry_out;
    }

    utils::trim(a);
    }

    template<class block_t>
    void add_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
    {
    using double_block_t = typename big_uint<block_t>::double_block_type;

    if (b.is_zero())
    return;

    if (a.is_zero())
    {
    a = b;
    return;
    }

    auto& a_data = a.data();
    auto const& b_data = b.data();
    const auto min_size = std::min(a_data.size(), b_data.size());

    auto carry = double_block_t{ 0 };

    // both a and b have data
    for (auto i = std::size_t{ 0 }; i != min_size; ++i)
    {
    carry += static_cast<double_block_t>(a_data[i]) + static_cast<double_block_t>(b_data[i]);
    a_data[i] = static_cast<block_t>(carry);
    carry >>= utils::digits<block_t>();
    }

    // ran out of data in a, copy over the rest of b
    a_data.insert(a_data.end(), b_data.begin() + min_size, b_data.end());

    // add carry
    for (auto i = min_size; i != a_data.size() && (carry != double_block_t{ 0 }); ++i)
    {
    carry += static_cast<double_block_t>(a_data[i]);
    a_data[i] = static_cast<block_t>(carry);
    carry >>= utils::digits<block_t>();
    }

    // extend a if necessary
    if (carry)
    a_data.push_back(static_cast<block_t>(carry));
    }

    template<class block_t>
    void sub_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
    {
    using double_block_t = typename big_uint<block_t>::double_block_type;

    if (b.is_zero())
    return;

    if (b > a)
    throw std::invalid_argument("cannot subtract larger value from smaller one.");

    debug::die_if(a.data().size() < b.data().size());

    auto& a_data = a.data();
    auto const& b_data = b.data();

    auto borrow = double_block_t{ 0 };

    // both a and b have data
    for (auto i = std::size_t{ 0 }; i != b_data.size(); ++i)
    {
    borrow = static_cast<double_block_t>(a_data[i]) - static_cast<double_block_t>(b_data[i]) - borrow;
    a_data[i] = static_cast<block_t>(borrow);
    borrow = (borrow >> utils::digits<block_t>()) & double_block_t { 1 };
    }

    // ran out of data in b, subtract borrow
    for (auto i = b_data.size(); i != a_data.size() && (borrow != double_block_t{ 0 }); ++i)
    {
    borrow = static_cast<double_block_t>(a_data[i]) - borrow;
    a_data[i] = static_cast<block_t>(borrow);
    borrow = (borrow >> utils::digits<block_t>()) & double_block_t { 1 };
    }

    utils::trim(a);
    }

    template<class block_t>
    void mul_assign(big_uint<block_t>& lhs, big_uint<block_t> const& rhs)
    {
    using double_block_t = typename big_uint<block_t>::double_block_type;

    if (lhs.is_zero()) return;
    if (rhs.is_zero()) { lhs.data().clear(); return; }

    if (rhs == 1u) return;
    if (lhs == 1u) { lhs = rhs; return; }

    // note: long multiplication relies on:
    // double_block_t holding (max<block_t>() * max<block_t>() + 2 * max<block_t>())
    // which is exactly the case if digits<double_block_t>() == 2 * digits<block_t>();

    {
    auto b = rhs; // TODO: we only need to copy this if lhs and rhs refer to the same object
    auto a = std::move(lhs);
    auto& c = lhs;

    auto const& a_data = a.data();
    auto const& b_data = b.data();
    auto& c_data = c.data();

    c_data.resize(a_data.size() + b_data.size());

    for (auto i = std::size_t{ 0 }; i != a_data.size(); ++i)
    {
    auto carry = double_block_t{ 0 };

    for (auto j = std::size_t{ 0 }; j != b_data.size(); ++j)
    {
    carry += static_cast<double_block_t>(a_data[i]) * static_cast<double_block_t>(b_data[j]);
    carry += c_data[i + j];
    c_data[i + j] = static_cast<block_t>(carry);
    carry >>= utils::digits<block_t>();
    }

    // c_data[i + b_data.size()] is always zero
    if (carry)
    c_data[i + b_data.size()] = static_cast<block_t>(carry);
    }

    utils::trim(c);
    }
    }

    template<class block_t>
    void divmod(big_uint<block_t>& quotient, big_uint<block_t>& remainder, big_uint<block_t> dividend, big_uint<block_t> divisor)
    {
    using double_block_t = typename big_uint<block_t>::double_block_type;

    quotient.data().clear();
    remainder.data().clear();

    debug::die_if(divisor.is_zero());
    debug::die_if(dividend < divisor);
    debug::die_if(dividend == divisor);
    debug::die_if(dividend.data().size() == 1u && divisor.data().size() == 1u);

    auto const get_num_leading_zeros = (block_t x)
    {
    debug::die_if(x == 0);

    auto count = std::size_t{ 0 };

    while (x != 0)
    {
    ++count;
    x >>= 1;
    }

    return utils::digits<block_t>() - count;
    };

    auto const promote = (double_block_t b) { return b << utils::digits<block_t>(); };
    auto const demote = (double_block_t b) { return b >> utils::digits<block_t>(); };
    auto const checked_sub = (block_t& out, block_t a, block_t b) { return ((out = a - b) > a); };

    {
    auto& d = divisor;
    auto& n = remainder;
    remainder = std::move(dividend);
    auto& q = quotient;

    q.data().resize(n.data().size() - d.data().size() + 1);

    // single digit divisor
    if (d.data().size() == 1)
    {
    auto k = double_block_t{ 0 };
    auto const v = d.data()[0];

    for (auto i = n.data().size(); i != 0; --i)
    {
    auto const index = i - 1;
    k = (k << utils::digits<block_t>()) + n.data()[index];
    q.data()[index] = static_cast<block_t>(k / v);
    k -= static_cast<double_block_t>(q.data()[index]) * v;
    }

    n.data().clear();

    if (k != 0)
    n.data().push_back(static_cast<block_t>(k));

    utils::trim(q);

    return;
    }

    auto const b = double_block_t{ 1 } << utils::digits<block_t>();
    auto const ns = n.data().size(); // m
    auto const ds = d.data().size(); // n

    auto shift = get_num_leading_zeros(d.data().back());
    d <<= shift;
    n <<= shift;

    if (n.data().size() == ns)
    n.data().push_back(block_t{ 0 });

    for (auto i_outer = (ns - ds) + 1; i_outer != 0; --i_outer)
    {
    auto const j = i_outer - 1;

    // take the top two blocks of n, divide by top block of d, calc remainder
    auto v = d.data()[ds - 1];
    auto n_block = static_cast<double_block_t>(promote(n.data()[j + ds]) | n.data()[j + ds - 1]);
    auto qhat = static_cast<double_block_t>(n_block / v);
    auto rhat = static_cast<double_block_t>(n_block - qhat * v);

    // q is too big or (looking at next block) remainder is smaller than what will be taken away
    while (qhat >= b || (qhat * d.data()[ds - 2]) > (promote(rhat) + n.data()[j + ds - 2]))
    {
    qhat -= 1; rhat += v;
    if (rhat >= b) break;
    }

    // qhat is now correct, or 1 too high (extremely rare)

    // multiply divisor by qhat and subtract from n
    auto underflow = false;
    {
    auto k = double_block_t{ 0 };
    for (auto i = std::size_t{ 0 }; i != ds; ++i)
    {
    auto const p = static_cast<double_block_t>(qhat * d.data()[i]);
    auto const t = static_cast<double_block_t>(n.data()[i + j] - k - static_cast<block_t>(p));
    n.data()[i + j] = static_cast<block_t>(t);
    k = static_cast<double_block_t>(demote(p) - (static_cast<std::make_signed_t<double_block_t>>(t) >> utils::digits<block_t>()));
    }

    if (k != 0)
    underflow |= checked_sub(n.data()[j + ds], n.data()[j + ds], static_cast<block_t>(k));
    }

    // set quotient
    q.data()[j] = static_cast<block_t>(qhat);

    // underflow! (qhat was 1 too high)
    // decrement q and add back one divisor to the remainder
    if (underflow)
    {
    q.data()[j] = q.data()[j] - 1;

    auto k = double_block_t{ 0 };
    for (auto i = std::size_t{ 0 }; i != ds; ++i)
    {
    auto const t = double_block_t{ n.data()[i + j] } + d.data()[i] + k;
    n.data()[i + j] = static_cast<block_t>(t);
    k = static_cast<double_block_t>(t >> utils::digits<block_t>());
    }

    n.data()[j + ds] += static_cast<block_t>(k);
    }
    }

    utils::trim(q);

    // shift remainder back
    utils::trim(n);
    n >>= shift;
    }
    }

    template<class block_t>
    void div_assign(big_uint<block_t>& lhs, big_uint<block_t> const& rhs)
    {
    using double_block_t = typename big_uint<block_t>::double_block_type;

    if (rhs.is_zero())
    throw std::invalid_argument("divisor cannot be zero.");

    if (lhs < rhs) { lhs.data().clear(); return; }
    if (lhs == rhs) { lhs.data().clear(); lhs.data().push_back(1); return; }

    if (lhs.data().size() == 1u && rhs.data().size() == 1u)
    {
    lhs = static_cast<block_t>(lhs.data()[0] / rhs.data()[0]);
    return;
    }

    {
    auto q = big_uint<block_t>();
    auto r = big_uint<block_t>();
    divmod(q, r, lhs, rhs);
    lhs = std::move(q);
    }
    }

    template<class block_t>
    void mod_assign(big_uint<block_t>& lhs, big_uint<block_t> const& rhs)
    {
    using double_block_t = typename big_uint<block_t>::double_block_type;

    if (rhs.is_zero())
    throw std::invalid_argument("divisor cannot be zero.");

    if (lhs < rhs) { return; }
    if (lhs == rhs) { lhs.data().clear(); return; }

    if (lhs.data().size() == 1u && rhs.data().size() == 1u)
    {
    lhs = static_cast<block_t>(lhs.data()[0] % rhs.data()[0]);
    utils::trim(lhs);
    return;
    }

    {
    auto q = big_uint<block_t>();
    auto r = big_uint<block_t>();
    divmod(q, r, lhs, rhs);
    lhs = std::move(r);
    }
    }

    // utility for testing (when we need both quotient and remainder)
    template<class block_t>
    void divmod_test(big_uint<block_t>& quotient, big_uint<block_t>& remainder, big_uint<block_t> const& dividend, big_uint<block_t> const& divisor)
    {
    quotient.data().clear();
    remainder.data().clear();

    if (divisor.is_zero())
    throw std::invalid_argument("divisor cannot be zero.");

    if (dividend < divisor) { remainder = dividend; return; }
    if (dividend == divisor) { quotient.data().push_back(1); return; }

    if (dividend.data().size() == 1u && divisor.data().size() == 1u)
    {
    quotient = static_cast<block_t>(dividend.data()[0] / divisor.data()[0]);
    remainder = static_cast<block_t>(dividend.data()[0] % divisor.data()[0]);

    utils::trim(remainder);
    return;
    }

    divmod(quotient, remainder, dividend, divisor);
    }

    } // ops

    } // math

    } // rsa




    rsa_math__utils.h



    #pragma once

    #include <algorithm>
    #include <cstdint>
    #include <limits>
    #include <type_traits>

    namespace rsa
    {

    namespace math
    {

    template<class block_t>
    class big_uint;

    namespace utils
    {

    template<class uint_t>
    constexpr bool is_uint_v = (std::is_integral_v<uint_t> && std::is_unsigned_v<uint_t> && !std::is_same_v<uint_t, bool>);

    template<class uint_t>
    using enable_if_uint_t = std::enable_if_t<is_uint_v<uint_t>>;


    template<class t>
    constexpr std::uint32_t digits()
    {
    return std::uint32_t(std::numeric_limits<t>::digits);
    }

    template<class t>
    constexpr t max()
    {
    return std::numeric_limits<t>::max();
    }


    template<class block_t>
    bool has_extra_empty_blocks(big_uint<block_t> const& a)
    {
    return
    (std::find_if(a.data().rbegin(), a.data().rend(),
    (block_t b) { return b != block_t{ 0 }; }).base() !=
    a.data().end());
    }

    template<class block_t>
    void trim(big_uint<block_t>& a)
    {
    a.data().erase(
    std::find_if(a.data().rbegin(), a.data().rend(),
    (block_t b) { return b != block_t{ 0 }; }).base(),
    a.data().end());
    }

    } // utils

    } // math

    } // rsa




    Any advice / feedback is welcome.



    Any tips for neatening the math::ops functions are especially appreciated. The divmod function is quite fiddly and contains a lot of casts, several of which are only necessary for smaller block sizes (i.e. std::uint8_ts and integer promotion don't mix well).



    (Templating the class on block size seemed like a good idea at the time, but now it just looks overcomplicated, so I'll probably just fix the block size to std::uint32_t in the class with a typedef.)



    Is there anything missing / dodgy looking / surprising?





    Suggested usage is just like a normal numeric type. (Note, however, that the various math operators require unsigned types, and don't work with signed ones).



    #include <limits>
    #include <iostream>

    int main()
    {
    auto a = rsa::math::big_uint_32(std::numeric_limits<std::uint64_t>::max());
    auto b = (a * a) + 2u * a;

    std::cout << std::hex;
    for (auto block : b.data())
    std::cout << block << " ";
    std::cout << std::endl;
    }




    Full repository.



    Online compiler.










    share|improve this question


























      up vote
      4
      down vote

      favorite
      1









      up vote
      4
      down vote

      favorite
      1






      1





      Here is a big unsigned integer type I intend to use for learning and "fun" with cryptography.



      It uses a std::vector for storing blocks of unsigned integers, the exact type of which can be changed with the template argument block_t. A double_block_t is used internally for the various math operations. This is selected automatically via a traits class (passing it as an individual template argument seemed unnecessary and error prone).



      The last block in the vector must never be zero. A zero value is represented by an empty vector.



      Some utility functions are omitted; the files below contain the most interesting parts of the code. Here are links to the full repository and a single-file online version.





      rsa_math__big_uint.h: The class definition - constructors, utility functions, and operators.



      #pragma once

      #include "rsa__debug.h"
      #include "rsa_math__utils.h"
      #include "rsa_math_ops__operations.h"

      #include <algorithm>
      #include <cstdint>
      #include <stdexcept>
      #include <type_traits>
      #include <vector>

      namespace rsa
      {

      namespace math
      {

      template<class block_t>
      struct block_traits;

      template<>
      struct block_traits<std::uint8_t> { using double_block_type = std::uint16_t; };

      template<>
      struct block_traits<std::uint16_t> { using double_block_type = std::uint32_t; };

      template<>
      struct block_traits<std::uint32_t> { using double_block_type = std::uint64_t; };


      template<class block_t>
      class big_uint
      {
      public:

      using block_type = block_t;
      using double_block_type = typename block_traits<block_type>::double_block_type;
      using data_type = std::vector<block_type>;
      using block_index_type = std::size_t;
      using bit_index_type = std::size_t;

      static_assert(utils::is_uint_v<block_type>, "`block_type` must be an unsigned integer.");
      static_assert(utils::is_uint_v<double_block_type>, "`double_block_type` must be an unsigned integer.");
      static_assert(utils::digits<double_block_type>() == 2 * utils::digits<block_type>(), "`double_block_type` have twice as many digits as `block_type`.");

      #pragma region constructors

      big_uint();

      template<class uint_t>
      explicit big_uint(uint_t n);

      big_uint(std::initializer_list<block_type> block_values);

      explicit big_uint(block_index_type block_count, block_type block_value);

      template<class inputit_t>
      explicit big_uint(inputit_t first, inputit_t last);

      big_uint(big_uint const&) = default;
      big_uint(big_uint&&) = default;

      #pragma endregion

      #pragma region assignment

      big_uint& operator=(big_uint const&) = default;
      big_uint& operator=(big_uint&&) = default;

      template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint& operator=(uint_t n);

      #pragma endregion

      #pragma region general

      template<class uint_t>
      uint_t to_uint() const;

      bool is_zero() const;

      bool get_bit(bit_index_type i) const;
      void set_bit(bit_index_type i, bool value);
      void flip_bit(bit_index_type i);

      bit_index_type get_most_significant_bit() const;

      data_type& data();
      data_type const& data() const;

      #pragma endregion

      #pragma region bitwise operators

      big_uint& operator&=(big_uint const& b);

      template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint& operator&=(uint_t n);

      big_uint& operator|=(big_uint const& b);

      template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint& operator|=(uint_t n);

      big_uint& operator^=(big_uint const& b);

      template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint& operator^=(uint_t n);

      big_uint& operator<<=(bit_index_type n);
      big_uint& operator>>=(bit_index_type n);

      #pragma endregion

      #pragma region math operators

      big_uint& operator+=(big_uint const& b);

      template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint& operator+=(uint_t n);

      big_uint& operator-=(big_uint const& b);

      template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint& operator-=(uint_t n);

      big_uint& operator*=(big_uint const& b);

      template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint& operator*=(uint_t n);

      big_uint& operator/=(big_uint b);

      template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint& operator/=(uint_t n);

      big_uint& operator%=(big_uint b);

      template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint& operator%=(uint_t n);

      big_uint& operator++();
      big_uint operator++(int);

      big_uint& operator--();
      big_uint operator--(int);

      #pragma endregion

      private:

      data_type m_data;
      };

      using big_uint_8 = big_uint<std::uint8_t>;
      using big_uint_16 = big_uint<std::uint16_t>;
      using big_uint_32 = big_uint<std::uint32_t>;

      #pragma region members - construct

      template<class block_t>
      big_uint<block_t>::big_uint()
      {

      }

      template<class block_t>
      template<class uint_t>
      big_uint<block_t>::big_uint(uint_t n):
      big_uint()
      {
      static_assert(utils::is_uint_v<uint_t>, "`uint_t` must be an unsigned integer.");

      // shifting by >= the number digits in the type is undefined behaviour.
      constexpr bool can_rshift = (utils::digits<block_type>() < utils::digits<uint_t>());

      while (n != uint_t{ 0 })
      {
      // integer promotion, conversion to greater rank, implicit conversion to block_type
      m_data.push_back(utils::max<block_type>() & n);

      if (can_rshift)
      n >>= utils::digits<block_type>();
      else
      n = uint_t{ 0 };
      }
      }

      template<class block_t>
      big_uint<block_t>::big_uint(std::initializer_list<block_type> block_values):
      m_data(block_values)
      {
      utils::trim(*this);
      }

      template<class block_t>
      big_uint<block_t>::big_uint(block_index_type block_count, block_type block_value):
      m_data(block_count, block_value)
      {
      utils::trim(*this);
      }

      template<class block_t>
      template<class inputit_t>
      big_uint<block_t>::big_uint(inputit_t first, inputit_t last):
      m_data(first, last)
      {
      utils::trim(*this);
      }

      #pragma endregion

      #pragma region members - general

      template<class block_t>
      template<class uint_t>
      uint_t big_uint<block_t>::to_uint() const
      {
      static_assert(utils::is_uint_v<uint_t>, "`uint_t` must be an unsigned integer.");

      // it's much easier to static_assert / throw here if uint_t may be too small.
      // checking the actual value would be a lot more work.
      {
      static_assert(utils::digits<block_t>() <= utils::digits<uint_t>(), "uint_t may be too small to represent this number.");

      if (m_data.size() * utils::digits<block_type>() > utils::digits<uint_t>())
      throw std::range_error("uint_t may be too small to represent this number.");
      }

      auto result = uint_t{ 0 };

      if (m_data.empty())
      return result;

      for (auto i = std::size_t{ 0 }; i != m_data.size(); ++i)
      result |= (uint_t{ m_data[i] } << (i * utils::digits<block_type>()));

      return result;
      }

      template<class block_t>
      bool big_uint<block_t>::is_zero() const
      {
      return m_data.empty();
      }

      template<class block_t>
      bool big_uint<block_t>::get_bit(bit_index_type i) const
      {
      auto block_index = i / utils::digits<block_type>();

      if (m_data.size() <= block_index)
      return false;

      auto block_bit = i - (block_index * utils::digits<block_type>());
      debug::die_if(block_bit >= utils::digits<block_type>());

      return bool((m_data[block_index] >> block_bit) & 1u);
      }

      template<class block_t>
      void big_uint<block_t>::set_bit(bit_index_type i, bool value)
      {
      auto block_index = i / utils::digits<block_type>();

      if (m_data.size() <= block_index)
      m_data.resize(block_index + 1u);

      auto block_bit = i - (block_index * utils::digits<block_type>());
      debug::die_if(block_bit >= utils::digits<block_type>());

      auto mask = block_type(block_type{ 1u } << block_bit);
      m_data[block_index] |= mask & block_type(block_type{ value } << block_bit);
      }

      template<class block_t>
      void big_uint<block_t>::flip_bit(bit_index_type i)
      {
      auto block_index = i / utils::digits<block_type>();

      if (m_data.size() <= block_index)
      m_data.resize(block_index + 1u);

      auto block_bit = i - (block_index * utils::digits<block_type>());
      debug::die_if(block_bit >= utils::digits<block_type>());

      auto mask = block_type(block_type{ 1u } << block_bit);
      m_data[block_index] ^= mask;
      }

      template<class block_t>
      typename big_uint<block_t>::bit_index_type big_uint<block_t>::get_most_significant_bit() const
      {
      if (is_zero())
      throw std::logic_error("number must not be zero.");

      auto block = m_data.back();
      auto count = std::uint8_t{ 0u };

      while (block != block_type{ 1u })
      {
      ++count;
      block >>= 1u;
      }

      return bit_index_type{ count + (m_data.size() - 1u) * utils::digits<block_type>() };
      }

      template<class block_t>
      typename big_uint<block_t>::data_type& big_uint<block_t>::data()
      {
      return m_data;
      }

      template<class block_t>
      typename big_uint<block_t>::data_type const& big_uint<block_t>::data() const
      {
      return m_data;
      }

      #pragma endregion

      #pragma region members - assignment

      template<class block_t>
      template<class uint_t, typename>
      big_uint<block_t>& big_uint<block_t>::operator=(uint_t n)
      {
      return (*this = big_uint(n));
      }

      #pragma endregion

      #pragma region members - bitwise operators

      template<class block_t>
      big_uint<block_t>& big_uint<block_t>::operator&=(big_uint const& b)
      {
      ops::bit_and_assign(*this, b);
      return *this;
      }

      template<class block_t>
      template<class uint_t, typename>
      big_uint<block_t>& big_uint<block_t>::operator&=(uint_t n)
      {
      return operator&=(big_uint(n));
      }

      template<class block_t>
      big_uint<block_t>& big_uint<block_t>::operator|=(big_uint const& b)
      {
      ops::bit_or_assign(*this, b);
      return *this;
      }

      template<class block_t>
      template<class uint_t, typename>
      big_uint<block_t>& big_uint<block_t>::operator|=(uint_t n)
      {
      return operator|=(big_uint(n));
      }

      template<class block_t>
      big_uint<block_t>& big_uint<block_t>::operator^=(big_uint const& b)
      {
      ops::bit_xor_assign(*this, b);
      return *this;
      }

      template<class block_t>
      template<class uint_t, typename>
      big_uint<block_t>& big_uint<block_t>::operator^=(uint_t n)
      {
      return operator^=(big_uint(n));
      }

      template<class block_t>
      big_uint<block_t>& big_uint<block_t>::operator<<=(bit_index_type n)
      {
      ops::lshift_assign(*this, n);
      return *this;
      }

      template<class block_t>
      big_uint<block_t>& big_uint<block_t>::operator>>=(bit_index_type n)
      {
      ops::rshift_assign(*this, n);
      return *this;
      }

      #pragma endregion

      #pragma region members - math operators

      template<class block_t>
      big_uint<block_t>& big_uint<block_t>::operator+=(big_uint const& b)
      {
      ops::add_assign(*this, b);
      return *this;
      }

      template<class block_t>
      template<class uint_t, typename>
      big_uint<block_t>& big_uint<block_t>::operator+=(uint_t n)
      {
      return operator+=(big_uint(n));
      }

      template<class block_t>
      big_uint<block_t>& big_uint<block_t>::operator-=(big_uint const& b)
      {
      ops::sub_assign(*this, b);
      return *this;
      }

      template<class block_t>
      template<class uint_t, typename>
      big_uint<block_t>& big_uint<block_t>::operator-=(uint_t n)
      {
      return operator-=(big_uint(n));
      }

      template<class block_t>
      big_uint<block_t>& big_uint<block_t>::operator*=(big_uint const& b)
      {
      ops::mul_assign(*this, b);
      return *this;
      }

      template<class block_t>
      template<class uint_t, typename>
      big_uint<block_t>& big_uint<block_t>::operator*=(uint_t n)
      {
      return operator*=(big_uint(n));
      }

      template<class block_t>
      big_uint<block_t>& big_uint<block_t>::operator/=(big_uint b)
      {
      ops::div_assign(*this, b);
      return *this;
      }

      template<class block_t>
      template<class uint_t, typename>
      big_uint<block_t>& big_uint<block_t>::operator/=(uint_t n)
      {
      return operator/=(big_uint(n));
      }

      template<class block_t>
      big_uint<block_t>& big_uint<block_t>::operator%=(big_uint b)
      {
      ops::mod_assign(*this, b);
      return *this;
      }

      template<class block_t>
      template<class uint_t, typename>
      big_uint<block_t>& big_uint<block_t>::operator%=(uint_t n)
      {
      return operator%=(big_uint(n));
      }

      template<class block_t>
      big_uint<block_t>& big_uint<block_t>::operator++()
      {
      return operator+=(1u);
      }

      template<class block_t>
      big_uint<block_t> big_uint<block_t>::operator++(int)
      {
      auto temp = *this;
      operator++();
      return temp;
      }

      template<class block_t>
      big_uint<block_t>& big_uint<block_t>::operator--()
      {
      return operator-=(1u);
      }

      template<class block_t>
      big_uint<block_t> big_uint<block_t>::operator--(int)
      {
      auto temp = *this;
      operator--();
      return temp;
      }

      #pragma endregion

      #pragma region comparison operators

      template<class block_t>
      bool operator==(big_uint<block_t> const& a, big_uint<block_t> const& b)
      {
      return std::equal(a.data().begin(), a.data().end(), b.data().begin(), b.data().end());
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      bool operator==(big_uint<block_t> const& a, uint_t b)
      {
      return (a == big_uint<block_t>(b));
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      bool operator==(uint_t a, big_uint<block_t> const& b)
      {
      return (big_uint<block_t>(a) == b);
      }

      template<class block_t>
      bool operator!=(big_uint<block_t> const& a, big_uint<block_t> const& b)
      {
      return !(a == b);
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      bool operator!=(big_uint<block_t> const& a, uint_t b)
      {
      return (a != big_uint<block_t>(b));
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      bool operator!=(uint_t a, big_uint<block_t> const& b)
      {
      return (big_uint<block_t>(a) != b);
      }

      template<class block_t>
      bool operator<(big_uint<block_t> const& a, big_uint<block_t> const& b)
      {
      if (a.data().size() < b.data().size())
      return true;

      if (b.data().size() < a.data().size())
      return false;

      return std::lexicographical_compare(a.data().rbegin(), a.data().rend(), b.data().rbegin(), b.data().rend());
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      bool operator<(big_uint<block_t> const& a, uint_t b)
      {
      return (a < big_uint<block_t>(b));
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      bool operator<(uint_t a, big_uint<block_t> const& b)
      {
      return (big_uint<block_t>(a) < b);
      }

      template<class block_t>
      bool operator>(big_uint<block_t> const& a, big_uint<block_t> const& b)
      {
      return (b < a);
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      bool operator>(big_uint<block_t> const& a, uint_t b)
      {
      return (a > big_uint<block_t>(b));
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      bool operator>(uint_t a, big_uint<block_t> const& b)
      {
      return (big_uint<block_t>(a) > b);
      }

      template<class block_t>
      bool operator<=(big_uint<block_t> const& a, big_uint<block_t> const& b)
      {
      return !(b < a);
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      bool operator<=(big_uint<block_t> const& a, uint_t b)
      {
      return (a <= big_uint<block_t>(b));
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      bool operator<=(uint_t a, big_uint<block_t> const& b)
      {
      return (big_uint<block_t>(a) <= b);
      }

      template<class block_t>
      bool operator>=(big_uint<block_t> const& a, big_uint<block_t> const& b)
      {
      return !(a < b);
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      bool operator>=(big_uint<block_t> const& a, uint_t b)
      {
      return (a >= big_uint<block_t>(b));
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      bool operator>=(uint_t a, big_uint<block_t> const& b)
      {
      return (big_uint<block_t>(a) >= b);
      }

      #pragma endregion

      #pragma region bitwise operators

      template<class block_t>
      big_uint<block_t> operator&(big_uint<block_t> a, big_uint<block_t> const& b)
      {
      return (a &= b);
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator&(big_uint<block_t> a, uint_t b)
      {
      return (a &= big_uint<block_t>(b));
      }

      template<class block_t>
      big_uint<block_t> operator|(big_uint<block_t> a, big_uint<block_t> const& b)
      {
      return (a |= b);
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator|(big_uint<block_t> a, uint_t b)
      {
      return (a |= big_uint<block_t>(b));
      }

      template<class block_t>
      big_uint<block_t> operator^(big_uint<block_t> a, big_uint<block_t> const& b)
      {
      return (a ^= b);
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator^(big_uint<block_t> a, uint_t b)
      {
      return (a ^= big_uint<block_t>(b));
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator<<(big_uint<block_t> a, uint_t b)
      {
      return (a <<= b);
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator>>(big_uint<block_t> a, uint_t b)
      {
      return (a >>= b);
      }

      #pragma endregion

      #pragma region math operators

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator+(big_uint<block_t> a, uint_t b)
      {
      return (a += big_uint<block_t>(b));
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator+(uint_t a, big_uint<block_t> const& b)
      {
      return (big_uint<block_t>(a) += b);
      }

      template<class block_t>
      big_uint<block_t> operator+(big_uint<block_t> a, big_uint<block_t> const& b)
      {
      return (a += b);
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator-(big_uint<block_t> a, uint_t b)
      {
      return (a -= big_uint<block_t>(b));
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator-(uint_t a, big_uint<block_t> const& b)
      {
      return (big_uint<block_t>(a) -= b);
      }

      template<class block_t>
      big_uint<block_t> operator-(big_uint<block_t> a, big_uint<block_t> const& b)
      {
      return (a -= b);
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator*(big_uint<block_t> a, uint_t b)
      {
      return (a *= big_uint<block_t>(b));
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator*(uint_t a, big_uint<block_t> const& b)
      {
      return (big_uint<block_t>(a) *= b);
      }

      template<class block_t>
      big_uint<block_t> operator*(big_uint<block_t> a, big_uint<block_t> const& b)
      {
      return (a *= b);
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator/(big_uint<block_t> a, uint_t b)
      {
      return (a /= big_uint<block_t>(b));
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator/(uint_t a, big_uint<block_t> const& b)
      {
      return (big_uint<block_t>(a) /= b);
      }

      template<class block_t>
      big_uint<block_t> operator/(big_uint<block_t> a, big_uint<block_t> const& b)
      {
      return (a /= b);
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator%(big_uint<block_t> a, uint_t b)
      {
      return (a %= big_uint<block_t>(b));
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator%(uint_t a, big_uint<block_t> const& b)
      {
      return (big_uint<block_t>(a) %= b);
      }

      template<class block_t>
      big_uint<block_t> operator%(big_uint<block_t> a, big_uint<block_t> const& b)
      {
      return (a %= b);
      }

      #pragma endregion

      } // math

      } // rsa




      rsa_math_ops__operations.h - The core of the various bitwise and math operations. The bitwise operations simply apply the operation to each block (with some fiddling for different sizes of vector). The math operations are implemented with a standard "apply and carry" approach. The division / modulus is based on the Hacker's Delight implementations of Knuth's Algorithm D.



      #pragma once

      #include "rsa__debug.h"
      #include "rsa_math__utils.h"

      #include <algorithm>
      #include <stdexcept>

      namespace rsa
      {

      namespace math
      {

      template<class block_t>
      class big_uint;

      namespace ops
      {

      template<class block_t>
      void bit_and_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
      {
      const auto min_size = std::min(a.data().size(), b.data().size());

      for (auto i = std::size_t{ 0 }; i != min_size; ++i)
      a.data()[i] &= b.data()[i];

      a.data().resize(min_size);
      }

      template<class block_t>
      void bit_or_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
      {
      const auto min_size = std::min(a.data().size(), b.data().size());

      for (auto i = std::size_t{ 0 }; i != min_size; ++i)
      a.data()[i] |= b.data()[i];

      std::copy(b.data().begin() + min_size, b.data().end(), std::back_inserter(a.data()));
      }

      template<class block_t>
      void bit_xor_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
      {
      const auto min_size = std::min(a.data().size(), b.data().size());

      for (auto i = std::size_t{ 0 }; i != min_size; ++i)
      a.data()[i] ^= b.data()[i];

      std::copy(b.data().begin() + min_size, b.data().end(), std::back_inserter(a.data()));

      utils::trim(a);
      }

      template<class block_t>
      void lshift_assign(big_uint<block_t>& a, typename big_uint<block_t>::bit_index_type n)
      {
      using bit_index_t = typename big_uint<block_t>::bit_index_type;
      constexpr auto block_digits = utils::digits<block_t>();

      if (n == bit_index_t{ 0 })
      return;

      if (a.is_zero())
      return;

      // shift by whole blocks
      if (n >= block_digits)
      {
      auto blocks = n / block_digits;
      a.data().insert(a.data().begin(), blocks, block_t{ 0 });

      n -= (blocks * block_digits);

      if (n == bit_index_t{ 0 })
      return;
      }

      debug::die_if(n >= block_digits);
      const auto carry_shift = block_t(block_digits - n);
      auto carry = block_t{ 0 };

      // shift by partial blocks
      for (auto& block : a.data())
      {
      // take high bits, shift them to low bits for next block (cast to fix type from integer promotion)
      const auto carry_out = block_t(block >> carry_shift);

      // shift low bits to high, apply carry bits
      block = (block << n) | carry;

      carry = carry_out;
      }

      if (carry != block_t{ 0 })
      a.data().push_back(carry);

      debug::die_if(utils::has_extra_empty_blocks(a));
      }

      template<class block_t>
      void rshift_assign(big_uint<block_t>& a, typename big_uint<block_t>::bit_index_type n)
      {
      using bit_index_t = typename big_uint<block_t>::bit_index_type;
      constexpr auto block_digits = utils::digits<block_t>();

      if (n == bit_index_t{ 0 })
      return;

      if (a.is_zero())
      return;

      // shift by whole blocks
      if (n >= block_digits)
      {
      auto blocks = n / block_digits;
      a.data().erase(a.data().begin(), a.data().begin() + std::min<std::size_t>(blocks, a.data().size()));

      if (a.is_zero())
      return;

      n -= (blocks * block_digits);

      if (n == bit_index_t{ 0 })
      return;
      }

      debug::die_if(n >= block_digits);
      const auto carry_shift = block_t(block_digits - n);
      auto carry = block_t{ 0 };

      // shift by partial blocks
      for (auto i_block = a.data().rbegin(); i_block != a.data().rend(); ++i_block)
      {
      auto& block = *i_block;

      // take low bits, shift them to high bits for the next block (cast to fix type from integer promotion)
      const auto carry_out = block_t(block << carry_shift);

      // shift high bits to low, apply carry bits
      block = (block >> n) | carry;

      carry = carry_out;
      }

      utils::trim(a);
      }

      template<class block_t>
      void add_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
      {
      using double_block_t = typename big_uint<block_t>::double_block_type;

      if (b.is_zero())
      return;

      if (a.is_zero())
      {
      a = b;
      return;
      }

      auto& a_data = a.data();
      auto const& b_data = b.data();
      const auto min_size = std::min(a_data.size(), b_data.size());

      auto carry = double_block_t{ 0 };

      // both a and b have data
      for (auto i = std::size_t{ 0 }; i != min_size; ++i)
      {
      carry += static_cast<double_block_t>(a_data[i]) + static_cast<double_block_t>(b_data[i]);
      a_data[i] = static_cast<block_t>(carry);
      carry >>= utils::digits<block_t>();
      }

      // ran out of data in a, copy over the rest of b
      a_data.insert(a_data.end(), b_data.begin() + min_size, b_data.end());

      // add carry
      for (auto i = min_size; i != a_data.size() && (carry != double_block_t{ 0 }); ++i)
      {
      carry += static_cast<double_block_t>(a_data[i]);
      a_data[i] = static_cast<block_t>(carry);
      carry >>= utils::digits<block_t>();
      }

      // extend a if necessary
      if (carry)
      a_data.push_back(static_cast<block_t>(carry));
      }

      template<class block_t>
      void sub_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
      {
      using double_block_t = typename big_uint<block_t>::double_block_type;

      if (b.is_zero())
      return;

      if (b > a)
      throw std::invalid_argument("cannot subtract larger value from smaller one.");

      debug::die_if(a.data().size() < b.data().size());

      auto& a_data = a.data();
      auto const& b_data = b.data();

      auto borrow = double_block_t{ 0 };

      // both a and b have data
      for (auto i = std::size_t{ 0 }; i != b_data.size(); ++i)
      {
      borrow = static_cast<double_block_t>(a_data[i]) - static_cast<double_block_t>(b_data[i]) - borrow;
      a_data[i] = static_cast<block_t>(borrow);
      borrow = (borrow >> utils::digits<block_t>()) & double_block_t { 1 };
      }

      // ran out of data in b, subtract borrow
      for (auto i = b_data.size(); i != a_data.size() && (borrow != double_block_t{ 0 }); ++i)
      {
      borrow = static_cast<double_block_t>(a_data[i]) - borrow;
      a_data[i] = static_cast<block_t>(borrow);
      borrow = (borrow >> utils::digits<block_t>()) & double_block_t { 1 };
      }

      utils::trim(a);
      }

      template<class block_t>
      void mul_assign(big_uint<block_t>& lhs, big_uint<block_t> const& rhs)
      {
      using double_block_t = typename big_uint<block_t>::double_block_type;

      if (lhs.is_zero()) return;
      if (rhs.is_zero()) { lhs.data().clear(); return; }

      if (rhs == 1u) return;
      if (lhs == 1u) { lhs = rhs; return; }

      // note: long multiplication relies on:
      // double_block_t holding (max<block_t>() * max<block_t>() + 2 * max<block_t>())
      // which is exactly the case if digits<double_block_t>() == 2 * digits<block_t>();

      {
      auto b = rhs; // TODO: we only need to copy this if lhs and rhs refer to the same object
      auto a = std::move(lhs);
      auto& c = lhs;

      auto const& a_data = a.data();
      auto const& b_data = b.data();
      auto& c_data = c.data();

      c_data.resize(a_data.size() + b_data.size());

      for (auto i = std::size_t{ 0 }; i != a_data.size(); ++i)
      {
      auto carry = double_block_t{ 0 };

      for (auto j = std::size_t{ 0 }; j != b_data.size(); ++j)
      {
      carry += static_cast<double_block_t>(a_data[i]) * static_cast<double_block_t>(b_data[j]);
      carry += c_data[i + j];
      c_data[i + j] = static_cast<block_t>(carry);
      carry >>= utils::digits<block_t>();
      }

      // c_data[i + b_data.size()] is always zero
      if (carry)
      c_data[i + b_data.size()] = static_cast<block_t>(carry);
      }

      utils::trim(c);
      }
      }

      template<class block_t>
      void divmod(big_uint<block_t>& quotient, big_uint<block_t>& remainder, big_uint<block_t> dividend, big_uint<block_t> divisor)
      {
      using double_block_t = typename big_uint<block_t>::double_block_type;

      quotient.data().clear();
      remainder.data().clear();

      debug::die_if(divisor.is_zero());
      debug::die_if(dividend < divisor);
      debug::die_if(dividend == divisor);
      debug::die_if(dividend.data().size() == 1u && divisor.data().size() == 1u);

      auto const get_num_leading_zeros = (block_t x)
      {
      debug::die_if(x == 0);

      auto count = std::size_t{ 0 };

      while (x != 0)
      {
      ++count;
      x >>= 1;
      }

      return utils::digits<block_t>() - count;
      };

      auto const promote = (double_block_t b) { return b << utils::digits<block_t>(); };
      auto const demote = (double_block_t b) { return b >> utils::digits<block_t>(); };
      auto const checked_sub = (block_t& out, block_t a, block_t b) { return ((out = a - b) > a); };

      {
      auto& d = divisor;
      auto& n = remainder;
      remainder = std::move(dividend);
      auto& q = quotient;

      q.data().resize(n.data().size() - d.data().size() + 1);

      // single digit divisor
      if (d.data().size() == 1)
      {
      auto k = double_block_t{ 0 };
      auto const v = d.data()[0];

      for (auto i = n.data().size(); i != 0; --i)
      {
      auto const index = i - 1;
      k = (k << utils::digits<block_t>()) + n.data()[index];
      q.data()[index] = static_cast<block_t>(k / v);
      k -= static_cast<double_block_t>(q.data()[index]) * v;
      }

      n.data().clear();

      if (k != 0)
      n.data().push_back(static_cast<block_t>(k));

      utils::trim(q);

      return;
      }

      auto const b = double_block_t{ 1 } << utils::digits<block_t>();
      auto const ns = n.data().size(); // m
      auto const ds = d.data().size(); // n

      auto shift = get_num_leading_zeros(d.data().back());
      d <<= shift;
      n <<= shift;

      if (n.data().size() == ns)
      n.data().push_back(block_t{ 0 });

      for (auto i_outer = (ns - ds) + 1; i_outer != 0; --i_outer)
      {
      auto const j = i_outer - 1;

      // take the top two blocks of n, divide by top block of d, calc remainder
      auto v = d.data()[ds - 1];
      auto n_block = static_cast<double_block_t>(promote(n.data()[j + ds]) | n.data()[j + ds - 1]);
      auto qhat = static_cast<double_block_t>(n_block / v);
      auto rhat = static_cast<double_block_t>(n_block - qhat * v);

      // q is too big or (looking at next block) remainder is smaller than what will be taken away
      while (qhat >= b || (qhat * d.data()[ds - 2]) > (promote(rhat) + n.data()[j + ds - 2]))
      {
      qhat -= 1; rhat += v;
      if (rhat >= b) break;
      }

      // qhat is now correct, or 1 too high (extremely rare)

      // multiply divisor by qhat and subtract from n
      auto underflow = false;
      {
      auto k = double_block_t{ 0 };
      for (auto i = std::size_t{ 0 }; i != ds; ++i)
      {
      auto const p = static_cast<double_block_t>(qhat * d.data()[i]);
      auto const t = static_cast<double_block_t>(n.data()[i + j] - k - static_cast<block_t>(p));
      n.data()[i + j] = static_cast<block_t>(t);
      k = static_cast<double_block_t>(demote(p) - (static_cast<std::make_signed_t<double_block_t>>(t) >> utils::digits<block_t>()));
      }

      if (k != 0)
      underflow |= checked_sub(n.data()[j + ds], n.data()[j + ds], static_cast<block_t>(k));
      }

      // set quotient
      q.data()[j] = static_cast<block_t>(qhat);

      // underflow! (qhat was 1 too high)
      // decrement q and add back one divisor to the remainder
      if (underflow)
      {
      q.data()[j] = q.data()[j] - 1;

      auto k = double_block_t{ 0 };
      for (auto i = std::size_t{ 0 }; i != ds; ++i)
      {
      auto const t = double_block_t{ n.data()[i + j] } + d.data()[i] + k;
      n.data()[i + j] = static_cast<block_t>(t);
      k = static_cast<double_block_t>(t >> utils::digits<block_t>());
      }

      n.data()[j + ds] += static_cast<block_t>(k);
      }
      }

      utils::trim(q);

      // shift remainder back
      utils::trim(n);
      n >>= shift;
      }
      }

      template<class block_t>
      void div_assign(big_uint<block_t>& lhs, big_uint<block_t> const& rhs)
      {
      using double_block_t = typename big_uint<block_t>::double_block_type;

      if (rhs.is_zero())
      throw std::invalid_argument("divisor cannot be zero.");

      if (lhs < rhs) { lhs.data().clear(); return; }
      if (lhs == rhs) { lhs.data().clear(); lhs.data().push_back(1); return; }

      if (lhs.data().size() == 1u && rhs.data().size() == 1u)
      {
      lhs = static_cast<block_t>(lhs.data()[0] / rhs.data()[0]);
      return;
      }

      {
      auto q = big_uint<block_t>();
      auto r = big_uint<block_t>();
      divmod(q, r, lhs, rhs);
      lhs = std::move(q);
      }
      }

      template<class block_t>
      void mod_assign(big_uint<block_t>& lhs, big_uint<block_t> const& rhs)
      {
      using double_block_t = typename big_uint<block_t>::double_block_type;

      if (rhs.is_zero())
      throw std::invalid_argument("divisor cannot be zero.");

      if (lhs < rhs) { return; }
      if (lhs == rhs) { lhs.data().clear(); return; }

      if (lhs.data().size() == 1u && rhs.data().size() == 1u)
      {
      lhs = static_cast<block_t>(lhs.data()[0] % rhs.data()[0]);
      utils::trim(lhs);
      return;
      }

      {
      auto q = big_uint<block_t>();
      auto r = big_uint<block_t>();
      divmod(q, r, lhs, rhs);
      lhs = std::move(r);
      }
      }

      // utility for testing (when we need both quotient and remainder)
      template<class block_t>
      void divmod_test(big_uint<block_t>& quotient, big_uint<block_t>& remainder, big_uint<block_t> const& dividend, big_uint<block_t> const& divisor)
      {
      quotient.data().clear();
      remainder.data().clear();

      if (divisor.is_zero())
      throw std::invalid_argument("divisor cannot be zero.");

      if (dividend < divisor) { remainder = dividend; return; }
      if (dividend == divisor) { quotient.data().push_back(1); return; }

      if (dividend.data().size() == 1u && divisor.data().size() == 1u)
      {
      quotient = static_cast<block_t>(dividend.data()[0] / divisor.data()[0]);
      remainder = static_cast<block_t>(dividend.data()[0] % divisor.data()[0]);

      utils::trim(remainder);
      return;
      }

      divmod(quotient, remainder, dividend, divisor);
      }

      } // ops

      } // math

      } // rsa




      rsa_math__utils.h



      #pragma once

      #include <algorithm>
      #include <cstdint>
      #include <limits>
      #include <type_traits>

      namespace rsa
      {

      namespace math
      {

      template<class block_t>
      class big_uint;

      namespace utils
      {

      template<class uint_t>
      constexpr bool is_uint_v = (std::is_integral_v<uint_t> && std::is_unsigned_v<uint_t> && !std::is_same_v<uint_t, bool>);

      template<class uint_t>
      using enable_if_uint_t = std::enable_if_t<is_uint_v<uint_t>>;


      template<class t>
      constexpr std::uint32_t digits()
      {
      return std::uint32_t(std::numeric_limits<t>::digits);
      }

      template<class t>
      constexpr t max()
      {
      return std::numeric_limits<t>::max();
      }


      template<class block_t>
      bool has_extra_empty_blocks(big_uint<block_t> const& a)
      {
      return
      (std::find_if(a.data().rbegin(), a.data().rend(),
      (block_t b) { return b != block_t{ 0 }; }).base() !=
      a.data().end());
      }

      template<class block_t>
      void trim(big_uint<block_t>& a)
      {
      a.data().erase(
      std::find_if(a.data().rbegin(), a.data().rend(),
      (block_t b) { return b != block_t{ 0 }; }).base(),
      a.data().end());
      }

      } // utils

      } // math

      } // rsa




      Any advice / feedback is welcome.



      Any tips for neatening the math::ops functions are especially appreciated. The divmod function is quite fiddly and contains a lot of casts, several of which are only necessary for smaller block sizes (i.e. std::uint8_ts and integer promotion don't mix well).



      (Templating the class on block size seemed like a good idea at the time, but now it just looks overcomplicated, so I'll probably just fix the block size to std::uint32_t in the class with a typedef.)



      Is there anything missing / dodgy looking / surprising?





      Suggested usage is just like a normal numeric type. (Note, however, that the various math operators require unsigned types, and don't work with signed ones).



      #include <limits>
      #include <iostream>

      int main()
      {
      auto a = rsa::math::big_uint_32(std::numeric_limits<std::uint64_t>::max());
      auto b = (a * a) + 2u * a;

      std::cout << std::hex;
      for (auto block : b.data())
      std::cout << block << " ";
      std::cout << std::endl;
      }




      Full repository.



      Online compiler.










      share|improve this question















      Here is a big unsigned integer type I intend to use for learning and "fun" with cryptography.



      It uses a std::vector for storing blocks of unsigned integers, the exact type of which can be changed with the template argument block_t. A double_block_t is used internally for the various math operations. This is selected automatically via a traits class (passing it as an individual template argument seemed unnecessary and error prone).



      The last block in the vector must never be zero. A zero value is represented by an empty vector.



      Some utility functions are omitted; the files below contain the most interesting parts of the code. Here are links to the full repository and a single-file online version.





      rsa_math__big_uint.h: The class definition - constructors, utility functions, and operators.



      #pragma once

      #include "rsa__debug.h"
      #include "rsa_math__utils.h"
      #include "rsa_math_ops__operations.h"

      #include <algorithm>
      #include <cstdint>
      #include <stdexcept>
      #include <type_traits>
      #include <vector>

      namespace rsa
      {

      namespace math
      {

      template<class block_t>
      struct block_traits;

      template<>
      struct block_traits<std::uint8_t> { using double_block_type = std::uint16_t; };

      template<>
      struct block_traits<std::uint16_t> { using double_block_type = std::uint32_t; };

      template<>
      struct block_traits<std::uint32_t> { using double_block_type = std::uint64_t; };


      template<class block_t>
      class big_uint
      {
      public:

      using block_type = block_t;
      using double_block_type = typename block_traits<block_type>::double_block_type;
      using data_type = std::vector<block_type>;
      using block_index_type = std::size_t;
      using bit_index_type = std::size_t;

      static_assert(utils::is_uint_v<block_type>, "`block_type` must be an unsigned integer.");
      static_assert(utils::is_uint_v<double_block_type>, "`double_block_type` must be an unsigned integer.");
      static_assert(utils::digits<double_block_type>() == 2 * utils::digits<block_type>(), "`double_block_type` have twice as many digits as `block_type`.");

      #pragma region constructors

      big_uint();

      template<class uint_t>
      explicit big_uint(uint_t n);

      big_uint(std::initializer_list<block_type> block_values);

      explicit big_uint(block_index_type block_count, block_type block_value);

      template<class inputit_t>
      explicit big_uint(inputit_t first, inputit_t last);

      big_uint(big_uint const&) = default;
      big_uint(big_uint&&) = default;

      #pragma endregion

      #pragma region assignment

      big_uint& operator=(big_uint const&) = default;
      big_uint& operator=(big_uint&&) = default;

      template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint& operator=(uint_t n);

      #pragma endregion

      #pragma region general

      template<class uint_t>
      uint_t to_uint() const;

      bool is_zero() const;

      bool get_bit(bit_index_type i) const;
      void set_bit(bit_index_type i, bool value);
      void flip_bit(bit_index_type i);

      bit_index_type get_most_significant_bit() const;

      data_type& data();
      data_type const& data() const;

      #pragma endregion

      #pragma region bitwise operators

      big_uint& operator&=(big_uint const& b);

      template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint& operator&=(uint_t n);

      big_uint& operator|=(big_uint const& b);

      template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint& operator|=(uint_t n);

      big_uint& operator^=(big_uint const& b);

      template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint& operator^=(uint_t n);

      big_uint& operator<<=(bit_index_type n);
      big_uint& operator>>=(bit_index_type n);

      #pragma endregion

      #pragma region math operators

      big_uint& operator+=(big_uint const& b);

      template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint& operator+=(uint_t n);

      big_uint& operator-=(big_uint const& b);

      template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint& operator-=(uint_t n);

      big_uint& operator*=(big_uint const& b);

      template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint& operator*=(uint_t n);

      big_uint& operator/=(big_uint b);

      template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint& operator/=(uint_t n);

      big_uint& operator%=(big_uint b);

      template<class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint& operator%=(uint_t n);

      big_uint& operator++();
      big_uint operator++(int);

      big_uint& operator--();
      big_uint operator--(int);

      #pragma endregion

      private:

      data_type m_data;
      };

      using big_uint_8 = big_uint<std::uint8_t>;
      using big_uint_16 = big_uint<std::uint16_t>;
      using big_uint_32 = big_uint<std::uint32_t>;

      #pragma region members - construct

      template<class block_t>
      big_uint<block_t>::big_uint()
      {

      }

      template<class block_t>
      template<class uint_t>
      big_uint<block_t>::big_uint(uint_t n):
      big_uint()
      {
      static_assert(utils::is_uint_v<uint_t>, "`uint_t` must be an unsigned integer.");

      // shifting by >= the number digits in the type is undefined behaviour.
      constexpr bool can_rshift = (utils::digits<block_type>() < utils::digits<uint_t>());

      while (n != uint_t{ 0 })
      {
      // integer promotion, conversion to greater rank, implicit conversion to block_type
      m_data.push_back(utils::max<block_type>() & n);

      if (can_rshift)
      n >>= utils::digits<block_type>();
      else
      n = uint_t{ 0 };
      }
      }

      template<class block_t>
      big_uint<block_t>::big_uint(std::initializer_list<block_type> block_values):
      m_data(block_values)
      {
      utils::trim(*this);
      }

      template<class block_t>
      big_uint<block_t>::big_uint(block_index_type block_count, block_type block_value):
      m_data(block_count, block_value)
      {
      utils::trim(*this);
      }

      template<class block_t>
      template<class inputit_t>
      big_uint<block_t>::big_uint(inputit_t first, inputit_t last):
      m_data(first, last)
      {
      utils::trim(*this);
      }

      #pragma endregion

      #pragma region members - general

      template<class block_t>
      template<class uint_t>
      uint_t big_uint<block_t>::to_uint() const
      {
      static_assert(utils::is_uint_v<uint_t>, "`uint_t` must be an unsigned integer.");

      // it's much easier to static_assert / throw here if uint_t may be too small.
      // checking the actual value would be a lot more work.
      {
      static_assert(utils::digits<block_t>() <= utils::digits<uint_t>(), "uint_t may be too small to represent this number.");

      if (m_data.size() * utils::digits<block_type>() > utils::digits<uint_t>())
      throw std::range_error("uint_t may be too small to represent this number.");
      }

      auto result = uint_t{ 0 };

      if (m_data.empty())
      return result;

      for (auto i = std::size_t{ 0 }; i != m_data.size(); ++i)
      result |= (uint_t{ m_data[i] } << (i * utils::digits<block_type>()));

      return result;
      }

      template<class block_t>
      bool big_uint<block_t>::is_zero() const
      {
      return m_data.empty();
      }

      template<class block_t>
      bool big_uint<block_t>::get_bit(bit_index_type i) const
      {
      auto block_index = i / utils::digits<block_type>();

      if (m_data.size() <= block_index)
      return false;

      auto block_bit = i - (block_index * utils::digits<block_type>());
      debug::die_if(block_bit >= utils::digits<block_type>());

      return bool((m_data[block_index] >> block_bit) & 1u);
      }

      template<class block_t>
      void big_uint<block_t>::set_bit(bit_index_type i, bool value)
      {
      auto block_index = i / utils::digits<block_type>();

      if (m_data.size() <= block_index)
      m_data.resize(block_index + 1u);

      auto block_bit = i - (block_index * utils::digits<block_type>());
      debug::die_if(block_bit >= utils::digits<block_type>());

      auto mask = block_type(block_type{ 1u } << block_bit);
      m_data[block_index] |= mask & block_type(block_type{ value } << block_bit);
      }

      template<class block_t>
      void big_uint<block_t>::flip_bit(bit_index_type i)
      {
      auto block_index = i / utils::digits<block_type>();

      if (m_data.size() <= block_index)
      m_data.resize(block_index + 1u);

      auto block_bit = i - (block_index * utils::digits<block_type>());
      debug::die_if(block_bit >= utils::digits<block_type>());

      auto mask = block_type(block_type{ 1u } << block_bit);
      m_data[block_index] ^= mask;
      }

      template<class block_t>
      typename big_uint<block_t>::bit_index_type big_uint<block_t>::get_most_significant_bit() const
      {
      if (is_zero())
      throw std::logic_error("number must not be zero.");

      auto block = m_data.back();
      auto count = std::uint8_t{ 0u };

      while (block != block_type{ 1u })
      {
      ++count;
      block >>= 1u;
      }

      return bit_index_type{ count + (m_data.size() - 1u) * utils::digits<block_type>() };
      }

      template<class block_t>
      typename big_uint<block_t>::data_type& big_uint<block_t>::data()
      {
      return m_data;
      }

      template<class block_t>
      typename big_uint<block_t>::data_type const& big_uint<block_t>::data() const
      {
      return m_data;
      }

      #pragma endregion

      #pragma region members - assignment

      template<class block_t>
      template<class uint_t, typename>
      big_uint<block_t>& big_uint<block_t>::operator=(uint_t n)
      {
      return (*this = big_uint(n));
      }

      #pragma endregion

      #pragma region members - bitwise operators

      template<class block_t>
      big_uint<block_t>& big_uint<block_t>::operator&=(big_uint const& b)
      {
      ops::bit_and_assign(*this, b);
      return *this;
      }

      template<class block_t>
      template<class uint_t, typename>
      big_uint<block_t>& big_uint<block_t>::operator&=(uint_t n)
      {
      return operator&=(big_uint(n));
      }

      template<class block_t>
      big_uint<block_t>& big_uint<block_t>::operator|=(big_uint const& b)
      {
      ops::bit_or_assign(*this, b);
      return *this;
      }

      template<class block_t>
      template<class uint_t, typename>
      big_uint<block_t>& big_uint<block_t>::operator|=(uint_t n)
      {
      return operator|=(big_uint(n));
      }

      template<class block_t>
      big_uint<block_t>& big_uint<block_t>::operator^=(big_uint const& b)
      {
      ops::bit_xor_assign(*this, b);
      return *this;
      }

      template<class block_t>
      template<class uint_t, typename>
      big_uint<block_t>& big_uint<block_t>::operator^=(uint_t n)
      {
      return operator^=(big_uint(n));
      }

      template<class block_t>
      big_uint<block_t>& big_uint<block_t>::operator<<=(bit_index_type n)
      {
      ops::lshift_assign(*this, n);
      return *this;
      }

      template<class block_t>
      big_uint<block_t>& big_uint<block_t>::operator>>=(bit_index_type n)
      {
      ops::rshift_assign(*this, n);
      return *this;
      }

      #pragma endregion

      #pragma region members - math operators

      template<class block_t>
      big_uint<block_t>& big_uint<block_t>::operator+=(big_uint const& b)
      {
      ops::add_assign(*this, b);
      return *this;
      }

      template<class block_t>
      template<class uint_t, typename>
      big_uint<block_t>& big_uint<block_t>::operator+=(uint_t n)
      {
      return operator+=(big_uint(n));
      }

      template<class block_t>
      big_uint<block_t>& big_uint<block_t>::operator-=(big_uint const& b)
      {
      ops::sub_assign(*this, b);
      return *this;
      }

      template<class block_t>
      template<class uint_t, typename>
      big_uint<block_t>& big_uint<block_t>::operator-=(uint_t n)
      {
      return operator-=(big_uint(n));
      }

      template<class block_t>
      big_uint<block_t>& big_uint<block_t>::operator*=(big_uint const& b)
      {
      ops::mul_assign(*this, b);
      return *this;
      }

      template<class block_t>
      template<class uint_t, typename>
      big_uint<block_t>& big_uint<block_t>::operator*=(uint_t n)
      {
      return operator*=(big_uint(n));
      }

      template<class block_t>
      big_uint<block_t>& big_uint<block_t>::operator/=(big_uint b)
      {
      ops::div_assign(*this, b);
      return *this;
      }

      template<class block_t>
      template<class uint_t, typename>
      big_uint<block_t>& big_uint<block_t>::operator/=(uint_t n)
      {
      return operator/=(big_uint(n));
      }

      template<class block_t>
      big_uint<block_t>& big_uint<block_t>::operator%=(big_uint b)
      {
      ops::mod_assign(*this, b);
      return *this;
      }

      template<class block_t>
      template<class uint_t, typename>
      big_uint<block_t>& big_uint<block_t>::operator%=(uint_t n)
      {
      return operator%=(big_uint(n));
      }

      template<class block_t>
      big_uint<block_t>& big_uint<block_t>::operator++()
      {
      return operator+=(1u);
      }

      template<class block_t>
      big_uint<block_t> big_uint<block_t>::operator++(int)
      {
      auto temp = *this;
      operator++();
      return temp;
      }

      template<class block_t>
      big_uint<block_t>& big_uint<block_t>::operator--()
      {
      return operator-=(1u);
      }

      template<class block_t>
      big_uint<block_t> big_uint<block_t>::operator--(int)
      {
      auto temp = *this;
      operator--();
      return temp;
      }

      #pragma endregion

      #pragma region comparison operators

      template<class block_t>
      bool operator==(big_uint<block_t> const& a, big_uint<block_t> const& b)
      {
      return std::equal(a.data().begin(), a.data().end(), b.data().begin(), b.data().end());
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      bool operator==(big_uint<block_t> const& a, uint_t b)
      {
      return (a == big_uint<block_t>(b));
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      bool operator==(uint_t a, big_uint<block_t> const& b)
      {
      return (big_uint<block_t>(a) == b);
      }

      template<class block_t>
      bool operator!=(big_uint<block_t> const& a, big_uint<block_t> const& b)
      {
      return !(a == b);
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      bool operator!=(big_uint<block_t> const& a, uint_t b)
      {
      return (a != big_uint<block_t>(b));
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      bool operator!=(uint_t a, big_uint<block_t> const& b)
      {
      return (big_uint<block_t>(a) != b);
      }

      template<class block_t>
      bool operator<(big_uint<block_t> const& a, big_uint<block_t> const& b)
      {
      if (a.data().size() < b.data().size())
      return true;

      if (b.data().size() < a.data().size())
      return false;

      return std::lexicographical_compare(a.data().rbegin(), a.data().rend(), b.data().rbegin(), b.data().rend());
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      bool operator<(big_uint<block_t> const& a, uint_t b)
      {
      return (a < big_uint<block_t>(b));
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      bool operator<(uint_t a, big_uint<block_t> const& b)
      {
      return (big_uint<block_t>(a) < b);
      }

      template<class block_t>
      bool operator>(big_uint<block_t> const& a, big_uint<block_t> const& b)
      {
      return (b < a);
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      bool operator>(big_uint<block_t> const& a, uint_t b)
      {
      return (a > big_uint<block_t>(b));
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      bool operator>(uint_t a, big_uint<block_t> const& b)
      {
      return (big_uint<block_t>(a) > b);
      }

      template<class block_t>
      bool operator<=(big_uint<block_t> const& a, big_uint<block_t> const& b)
      {
      return !(b < a);
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      bool operator<=(big_uint<block_t> const& a, uint_t b)
      {
      return (a <= big_uint<block_t>(b));
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      bool operator<=(uint_t a, big_uint<block_t> const& b)
      {
      return (big_uint<block_t>(a) <= b);
      }

      template<class block_t>
      bool operator>=(big_uint<block_t> const& a, big_uint<block_t> const& b)
      {
      return !(a < b);
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      bool operator>=(big_uint<block_t> const& a, uint_t b)
      {
      return (a >= big_uint<block_t>(b));
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      bool operator>=(uint_t a, big_uint<block_t> const& b)
      {
      return (big_uint<block_t>(a) >= b);
      }

      #pragma endregion

      #pragma region bitwise operators

      template<class block_t>
      big_uint<block_t> operator&(big_uint<block_t> a, big_uint<block_t> const& b)
      {
      return (a &= b);
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator&(big_uint<block_t> a, uint_t b)
      {
      return (a &= big_uint<block_t>(b));
      }

      template<class block_t>
      big_uint<block_t> operator|(big_uint<block_t> a, big_uint<block_t> const& b)
      {
      return (a |= b);
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator|(big_uint<block_t> a, uint_t b)
      {
      return (a |= big_uint<block_t>(b));
      }

      template<class block_t>
      big_uint<block_t> operator^(big_uint<block_t> a, big_uint<block_t> const& b)
      {
      return (a ^= b);
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator^(big_uint<block_t> a, uint_t b)
      {
      return (a ^= big_uint<block_t>(b));
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator<<(big_uint<block_t> a, uint_t b)
      {
      return (a <<= b);
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator>>(big_uint<block_t> a, uint_t b)
      {
      return (a >>= b);
      }

      #pragma endregion

      #pragma region math operators

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator+(big_uint<block_t> a, uint_t b)
      {
      return (a += big_uint<block_t>(b));
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator+(uint_t a, big_uint<block_t> const& b)
      {
      return (big_uint<block_t>(a) += b);
      }

      template<class block_t>
      big_uint<block_t> operator+(big_uint<block_t> a, big_uint<block_t> const& b)
      {
      return (a += b);
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator-(big_uint<block_t> a, uint_t b)
      {
      return (a -= big_uint<block_t>(b));
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator-(uint_t a, big_uint<block_t> const& b)
      {
      return (big_uint<block_t>(a) -= b);
      }

      template<class block_t>
      big_uint<block_t> operator-(big_uint<block_t> a, big_uint<block_t> const& b)
      {
      return (a -= b);
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator*(big_uint<block_t> a, uint_t b)
      {
      return (a *= big_uint<block_t>(b));
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator*(uint_t a, big_uint<block_t> const& b)
      {
      return (big_uint<block_t>(a) *= b);
      }

      template<class block_t>
      big_uint<block_t> operator*(big_uint<block_t> a, big_uint<block_t> const& b)
      {
      return (a *= b);
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator/(big_uint<block_t> a, uint_t b)
      {
      return (a /= big_uint<block_t>(b));
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator/(uint_t a, big_uint<block_t> const& b)
      {
      return (big_uint<block_t>(a) /= b);
      }

      template<class block_t>
      big_uint<block_t> operator/(big_uint<block_t> a, big_uint<block_t> const& b)
      {
      return (a /= b);
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator%(big_uint<block_t> a, uint_t b)
      {
      return (a %= big_uint<block_t>(b));
      }

      template<class block_t, class uint_t, typename = utils::enable_if_uint_t<uint_t>>
      big_uint<block_t> operator%(uint_t a, big_uint<block_t> const& b)
      {
      return (big_uint<block_t>(a) %= b);
      }

      template<class block_t>
      big_uint<block_t> operator%(big_uint<block_t> a, big_uint<block_t> const& b)
      {
      return (a %= b);
      }

      #pragma endregion

      } // math

      } // rsa




      rsa_math_ops__operations.h - The core of the various bitwise and math operations. The bitwise operations simply apply the operation to each block (with some fiddling for different sizes of vector). The math operations are implemented with a standard "apply and carry" approach. The division / modulus is based on the Hacker's Delight implementations of Knuth's Algorithm D.



      #pragma once

      #include "rsa__debug.h"
      #include "rsa_math__utils.h"

      #include <algorithm>
      #include <stdexcept>

      namespace rsa
      {

      namespace math
      {

      template<class block_t>
      class big_uint;

      namespace ops
      {

      template<class block_t>
      void bit_and_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
      {
      const auto min_size = std::min(a.data().size(), b.data().size());

      for (auto i = std::size_t{ 0 }; i != min_size; ++i)
      a.data()[i] &= b.data()[i];

      a.data().resize(min_size);
      }

      template<class block_t>
      void bit_or_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
      {
      const auto min_size = std::min(a.data().size(), b.data().size());

      for (auto i = std::size_t{ 0 }; i != min_size; ++i)
      a.data()[i] |= b.data()[i];

      std::copy(b.data().begin() + min_size, b.data().end(), std::back_inserter(a.data()));
      }

      template<class block_t>
      void bit_xor_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
      {
      const auto min_size = std::min(a.data().size(), b.data().size());

      for (auto i = std::size_t{ 0 }; i != min_size; ++i)
      a.data()[i] ^= b.data()[i];

      std::copy(b.data().begin() + min_size, b.data().end(), std::back_inserter(a.data()));

      utils::trim(a);
      }

      template<class block_t>
      void lshift_assign(big_uint<block_t>& a, typename big_uint<block_t>::bit_index_type n)
      {
      using bit_index_t = typename big_uint<block_t>::bit_index_type;
      constexpr auto block_digits = utils::digits<block_t>();

      if (n == bit_index_t{ 0 })
      return;

      if (a.is_zero())
      return;

      // shift by whole blocks
      if (n >= block_digits)
      {
      auto blocks = n / block_digits;
      a.data().insert(a.data().begin(), blocks, block_t{ 0 });

      n -= (blocks * block_digits);

      if (n == bit_index_t{ 0 })
      return;
      }

      debug::die_if(n >= block_digits);
      const auto carry_shift = block_t(block_digits - n);
      auto carry = block_t{ 0 };

      // shift by partial blocks
      for (auto& block : a.data())
      {
      // take high bits, shift them to low bits for next block (cast to fix type from integer promotion)
      const auto carry_out = block_t(block >> carry_shift);

      // shift low bits to high, apply carry bits
      block = (block << n) | carry;

      carry = carry_out;
      }

      if (carry != block_t{ 0 })
      a.data().push_back(carry);

      debug::die_if(utils::has_extra_empty_blocks(a));
      }

      template<class block_t>
      void rshift_assign(big_uint<block_t>& a, typename big_uint<block_t>::bit_index_type n)
      {
      using bit_index_t = typename big_uint<block_t>::bit_index_type;
      constexpr auto block_digits = utils::digits<block_t>();

      if (n == bit_index_t{ 0 })
      return;

      if (a.is_zero())
      return;

      // shift by whole blocks
      if (n >= block_digits)
      {
      auto blocks = n / block_digits;
      a.data().erase(a.data().begin(), a.data().begin() + std::min<std::size_t>(blocks, a.data().size()));

      if (a.is_zero())
      return;

      n -= (blocks * block_digits);

      if (n == bit_index_t{ 0 })
      return;
      }

      debug::die_if(n >= block_digits);
      const auto carry_shift = block_t(block_digits - n);
      auto carry = block_t{ 0 };

      // shift by partial blocks
      for (auto i_block = a.data().rbegin(); i_block != a.data().rend(); ++i_block)
      {
      auto& block = *i_block;

      // take low bits, shift them to high bits for the next block (cast to fix type from integer promotion)
      const auto carry_out = block_t(block << carry_shift);

      // shift high bits to low, apply carry bits
      block = (block >> n) | carry;

      carry = carry_out;
      }

      utils::trim(a);
      }

      template<class block_t>
      void add_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
      {
      using double_block_t = typename big_uint<block_t>::double_block_type;

      if (b.is_zero())
      return;

      if (a.is_zero())
      {
      a = b;
      return;
      }

      auto& a_data = a.data();
      auto const& b_data = b.data();
      const auto min_size = std::min(a_data.size(), b_data.size());

      auto carry = double_block_t{ 0 };

      // both a and b have data
      for (auto i = std::size_t{ 0 }; i != min_size; ++i)
      {
      carry += static_cast<double_block_t>(a_data[i]) + static_cast<double_block_t>(b_data[i]);
      a_data[i] = static_cast<block_t>(carry);
      carry >>= utils::digits<block_t>();
      }

      // ran out of data in a, copy over the rest of b
      a_data.insert(a_data.end(), b_data.begin() + min_size, b_data.end());

      // add carry
      for (auto i = min_size; i != a_data.size() && (carry != double_block_t{ 0 }); ++i)
      {
      carry += static_cast<double_block_t>(a_data[i]);
      a_data[i] = static_cast<block_t>(carry);
      carry >>= utils::digits<block_t>();
      }

      // extend a if necessary
      if (carry)
      a_data.push_back(static_cast<block_t>(carry));
      }

      template<class block_t>
      void sub_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
      {
      using double_block_t = typename big_uint<block_t>::double_block_type;

      if (b.is_zero())
      return;

      if (b > a)
      throw std::invalid_argument("cannot subtract larger value from smaller one.");

      debug::die_if(a.data().size() < b.data().size());

      auto& a_data = a.data();
      auto const& b_data = b.data();

      auto borrow = double_block_t{ 0 };

      // both a and b have data
      for (auto i = std::size_t{ 0 }; i != b_data.size(); ++i)
      {
      borrow = static_cast<double_block_t>(a_data[i]) - static_cast<double_block_t>(b_data[i]) - borrow;
      a_data[i] = static_cast<block_t>(borrow);
      borrow = (borrow >> utils::digits<block_t>()) & double_block_t { 1 };
      }

      // ran out of data in b, subtract borrow
      for (auto i = b_data.size(); i != a_data.size() && (borrow != double_block_t{ 0 }); ++i)
      {
      borrow = static_cast<double_block_t>(a_data[i]) - borrow;
      a_data[i] = static_cast<block_t>(borrow);
      borrow = (borrow >> utils::digits<block_t>()) & double_block_t { 1 };
      }

      utils::trim(a);
      }

      template<class block_t>
      void mul_assign(big_uint<block_t>& lhs, big_uint<block_t> const& rhs)
      {
      using double_block_t = typename big_uint<block_t>::double_block_type;

      if (lhs.is_zero()) return;
      if (rhs.is_zero()) { lhs.data().clear(); return; }

      if (rhs == 1u) return;
      if (lhs == 1u) { lhs = rhs; return; }

      // note: long multiplication relies on:
      // double_block_t holding (max<block_t>() * max<block_t>() + 2 * max<block_t>())
      // which is exactly the case if digits<double_block_t>() == 2 * digits<block_t>();

      {
      auto b = rhs; // TODO: we only need to copy this if lhs and rhs refer to the same object
      auto a = std::move(lhs);
      auto& c = lhs;

      auto const& a_data = a.data();
      auto const& b_data = b.data();
      auto& c_data = c.data();

      c_data.resize(a_data.size() + b_data.size());

      for (auto i = std::size_t{ 0 }; i != a_data.size(); ++i)
      {
      auto carry = double_block_t{ 0 };

      for (auto j = std::size_t{ 0 }; j != b_data.size(); ++j)
      {
      carry += static_cast<double_block_t>(a_data[i]) * static_cast<double_block_t>(b_data[j]);
      carry += c_data[i + j];
      c_data[i + j] = static_cast<block_t>(carry);
      carry >>= utils::digits<block_t>();
      }

      // c_data[i + b_data.size()] is always zero
      if (carry)
      c_data[i + b_data.size()] = static_cast<block_t>(carry);
      }

      utils::trim(c);
      }
      }

      template<class block_t>
      void divmod(big_uint<block_t>& quotient, big_uint<block_t>& remainder, big_uint<block_t> dividend, big_uint<block_t> divisor)
      {
      using double_block_t = typename big_uint<block_t>::double_block_type;

      quotient.data().clear();
      remainder.data().clear();

      debug::die_if(divisor.is_zero());
      debug::die_if(dividend < divisor);
      debug::die_if(dividend == divisor);
      debug::die_if(dividend.data().size() == 1u && divisor.data().size() == 1u);

      auto const get_num_leading_zeros = (block_t x)
      {
      debug::die_if(x == 0);

      auto count = std::size_t{ 0 };

      while (x != 0)
      {
      ++count;
      x >>= 1;
      }

      return utils::digits<block_t>() - count;
      };

      auto const promote = (double_block_t b) { return b << utils::digits<block_t>(); };
      auto const demote = (double_block_t b) { return b >> utils::digits<block_t>(); };
      auto const checked_sub = (block_t& out, block_t a, block_t b) { return ((out = a - b) > a); };

      {
      auto& d = divisor;
      auto& n = remainder;
      remainder = std::move(dividend);
      auto& q = quotient;

      q.data().resize(n.data().size() - d.data().size() + 1);

      // single digit divisor
      if (d.data().size() == 1)
      {
      auto k = double_block_t{ 0 };
      auto const v = d.data()[0];

      for (auto i = n.data().size(); i != 0; --i)
      {
      auto const index = i - 1;
      k = (k << utils::digits<block_t>()) + n.data()[index];
      q.data()[index] = static_cast<block_t>(k / v);
      k -= static_cast<double_block_t>(q.data()[index]) * v;
      }

      n.data().clear();

      if (k != 0)
      n.data().push_back(static_cast<block_t>(k));

      utils::trim(q);

      return;
      }

      auto const b = double_block_t{ 1 } << utils::digits<block_t>();
      auto const ns = n.data().size(); // m
      auto const ds = d.data().size(); // n

      auto shift = get_num_leading_zeros(d.data().back());
      d <<= shift;
      n <<= shift;

      if (n.data().size() == ns)
      n.data().push_back(block_t{ 0 });

      for (auto i_outer = (ns - ds) + 1; i_outer != 0; --i_outer)
      {
      auto const j = i_outer - 1;

      // take the top two blocks of n, divide by top block of d, calc remainder
      auto v = d.data()[ds - 1];
      auto n_block = static_cast<double_block_t>(promote(n.data()[j + ds]) | n.data()[j + ds - 1]);
      auto qhat = static_cast<double_block_t>(n_block / v);
      auto rhat = static_cast<double_block_t>(n_block - qhat * v);

      // q is too big or (looking at next block) remainder is smaller than what will be taken away
      while (qhat >= b || (qhat * d.data()[ds - 2]) > (promote(rhat) + n.data()[j + ds - 2]))
      {
      qhat -= 1; rhat += v;
      if (rhat >= b) break;
      }

      // qhat is now correct, or 1 too high (extremely rare)

      // multiply divisor by qhat and subtract from n
      auto underflow = false;
      {
      auto k = double_block_t{ 0 };
      for (auto i = std::size_t{ 0 }; i != ds; ++i)
      {
      auto const p = static_cast<double_block_t>(qhat * d.data()[i]);
      auto const t = static_cast<double_block_t>(n.data()[i + j] - k - static_cast<block_t>(p));
      n.data()[i + j] = static_cast<block_t>(t);
      k = static_cast<double_block_t>(demote(p) - (static_cast<std::make_signed_t<double_block_t>>(t) >> utils::digits<block_t>()));
      }

      if (k != 0)
      underflow |= checked_sub(n.data()[j + ds], n.data()[j + ds], static_cast<block_t>(k));
      }

      // set quotient
      q.data()[j] = static_cast<block_t>(qhat);

      // underflow! (qhat was 1 too high)
      // decrement q and add back one divisor to the remainder
      if (underflow)
      {
      q.data()[j] = q.data()[j] - 1;

      auto k = double_block_t{ 0 };
      for (auto i = std::size_t{ 0 }; i != ds; ++i)
      {
      auto const t = double_block_t{ n.data()[i + j] } + d.data()[i] + k;
      n.data()[i + j] = static_cast<block_t>(t);
      k = static_cast<double_block_t>(t >> utils::digits<block_t>());
      }

      n.data()[j + ds] += static_cast<block_t>(k);
      }
      }

      utils::trim(q);

      // shift remainder back
      utils::trim(n);
      n >>= shift;
      }
      }

      template<class block_t>
      void div_assign(big_uint<block_t>& lhs, big_uint<block_t> const& rhs)
      {
      using double_block_t = typename big_uint<block_t>::double_block_type;

      if (rhs.is_zero())
      throw std::invalid_argument("divisor cannot be zero.");

      if (lhs < rhs) { lhs.data().clear(); return; }
      if (lhs == rhs) { lhs.data().clear(); lhs.data().push_back(1); return; }

      if (lhs.data().size() == 1u && rhs.data().size() == 1u)
      {
      lhs = static_cast<block_t>(lhs.data()[0] / rhs.data()[0]);
      return;
      }

      {
      auto q = big_uint<block_t>();
      auto r = big_uint<block_t>();
      divmod(q, r, lhs, rhs);
      lhs = std::move(q);
      }
      }

      template<class block_t>
      void mod_assign(big_uint<block_t>& lhs, big_uint<block_t> const& rhs)
      {
      using double_block_t = typename big_uint<block_t>::double_block_type;

      if (rhs.is_zero())
      throw std::invalid_argument("divisor cannot be zero.");

      if (lhs < rhs) { return; }
      if (lhs == rhs) { lhs.data().clear(); return; }

      if (lhs.data().size() == 1u && rhs.data().size() == 1u)
      {
      lhs = static_cast<block_t>(lhs.data()[0] % rhs.data()[0]);
      utils::trim(lhs);
      return;
      }

      {
      auto q = big_uint<block_t>();
      auto r = big_uint<block_t>();
      divmod(q, r, lhs, rhs);
      lhs = std::move(r);
      }
      }

      // utility for testing (when we need both quotient and remainder)
      template<class block_t>
      void divmod_test(big_uint<block_t>& quotient, big_uint<block_t>& remainder, big_uint<block_t> const& dividend, big_uint<block_t> const& divisor)
      {
      quotient.data().clear();
      remainder.data().clear();

      if (divisor.is_zero())
      throw std::invalid_argument("divisor cannot be zero.");

      if (dividend < divisor) { remainder = dividend; return; }
      if (dividend == divisor) { quotient.data().push_back(1); return; }

      if (dividend.data().size() == 1u && divisor.data().size() == 1u)
      {
      quotient = static_cast<block_t>(dividend.data()[0] / divisor.data()[0]);
      remainder = static_cast<block_t>(dividend.data()[0] % divisor.data()[0]);

      utils::trim(remainder);
      return;
      }

      divmod(quotient, remainder, dividend, divisor);
      }

      } // ops

      } // math

      } // rsa




      rsa_math__utils.h



      #pragma once

      #include <algorithm>
      #include <cstdint>
      #include <limits>
      #include <type_traits>

      namespace rsa
      {

      namespace math
      {

      template<class block_t>
      class big_uint;

      namespace utils
      {

      template<class uint_t>
      constexpr bool is_uint_v = (std::is_integral_v<uint_t> && std::is_unsigned_v<uint_t> && !std::is_same_v<uint_t, bool>);

      template<class uint_t>
      using enable_if_uint_t = std::enable_if_t<is_uint_v<uint_t>>;


      template<class t>
      constexpr std::uint32_t digits()
      {
      return std::uint32_t(std::numeric_limits<t>::digits);
      }

      template<class t>
      constexpr t max()
      {
      return std::numeric_limits<t>::max();
      }


      template<class block_t>
      bool has_extra_empty_blocks(big_uint<block_t> const& a)
      {
      return
      (std::find_if(a.data().rbegin(), a.data().rend(),
      (block_t b) { return b != block_t{ 0 }; }).base() !=
      a.data().end());
      }

      template<class block_t>
      void trim(big_uint<block_t>& a)
      {
      a.data().erase(
      std::find_if(a.data().rbegin(), a.data().rend(),
      (block_t b) { return b != block_t{ 0 }; }).base(),
      a.data().end());
      }

      } // utils

      } // math

      } // rsa




      Any advice / feedback is welcome.



      Any tips for neatening the math::ops functions are especially appreciated. The divmod function is quite fiddly and contains a lot of casts, several of which are only necessary for smaller block sizes (i.e. std::uint8_ts and integer promotion don't mix well).



      (Templating the class on block size seemed like a good idea at the time, but now it just looks overcomplicated, so I'll probably just fix the block size to std::uint32_t in the class with a typedef.)



      Is there anything missing / dodgy looking / surprising?





      Suggested usage is just like a normal numeric type. (Note, however, that the various math operators require unsigned types, and don't work with signed ones).



      #include <limits>
      #include <iostream>

      int main()
      {
      auto a = rsa::math::big_uint_32(std::numeric_limits<std::uint64_t>::max());
      auto b = (a * a) + 2u * a;

      std::cout << std::hex;
      for (auto block : b.data())
      std::cout << block << " ";
      std::cout << std::endl;
      }




      Full repository.



      Online compiler.







      c++ integer






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Nov 18 at 18:18

























      asked Nov 18 at 13:43









      user673679

      2,066724




      2,066724






















          1 Answer
          1






          active

          oldest

          votes

















          up vote
          1
          down vote



          accepted










          Nice code; I didn't find any serious issues, so my comments are mostly limited to mere nitpicking.



          namespace math::util



          In has_extra_empty_blocks(), there's no need to continue searching if we don't have a match to begin with:




                  template<class block_t>
          bool has_extra_empty_blocks(big_uint<block_t> const& a)
          {
          return
          (std::find_if(a.data().rbegin(), a.data().rend(),
          (block_t b) { return b != block_t{ 0 }; }).base() !=
          a.data().end());
          }



          (Also, it's simpler to just compare the reverse iterator against a.data().rbegin() than to convert to forward iterator). We just want to check that the last element (if there is one) is zero:



                  template<class block_t>
          bool has_extra_empty_blocks(big_uint<block_t> const& a)
          {
          return !a.empty() && a.back() == block_t{0};
          }


          That looks much more readable, as well as being more efficient.



          I think that has_extra_empty_blocks() and trim() would probably make more sense as member functions of big_uint.



          namespace math::ops



          These functions all look like they should be members of big_uint (generally, inlined into the respective operators); that should obviate the need to expose data() publicly.



          Loops like this:




                      for (auto i = std::size_t{ 0 }; i != min_size; ++i)
          a.data()[i] &= b.data()[i];



          look like they would be more naturally written using std::transform():



                  template<class block_t>
          void bit_and_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
          {
          if (a.data().size() > b.data().size())
          a.data().resize(b.data().size());

          std::transform(a.data().begin(), a.data().end(), b.data().begin(),
          a.data().begin(), std::bit_and<block_t>{});
          }

          template<class block_t>
          void bit_or_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
          {
          if (a.data().size() < b.data().size())
          a.data().resize(b.data().size());

          std::transform(b.data().begin(), b.data().end(), a.data().begin(),
          a.data().begin(), std::bit_or<block_t>{});
          }

          template<class block_t>
          void bit_xor_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
          {
          if (a.data().size() < b.data().size())
          a.data().resize(b.data().size());

          std::transform(b.data().begin(), b.data().end(), a.data().begin(),
          a.data().begin(), std::bit_xor<block_t>{});

          utils::trim(a);
          }


          The shift operators repeat the test of n == 0 before and after shifting by units of block_digits. This could be simplified:



                      if (a.is_zero())
          return;

          if (n >= block_digits)
          {
          ... shift whole blocks
          }

          if (n == bit_index_t{ 0 })
          return;


          Addition and subtraction don't need twice the width of block_t. Carry is only ever one bit, and unsigned overflow is well-defined:



                      for (auto i = std::size_t{ 0 }; i != min_size; ++i)
          {
          bool carry_out = a_data[i] >= ~b_data[i] + !carry;
          a_data[i] += b_data[i] + carry;
          carry = carry_out;
          }


          It is possible to implement multiplication without double_block_t, but I'm not sure that it's worth the effort. Something to consider, perhaps only when double_block_t isn't defined?



          Both div_assign and mod_assign declare double_block_t, but never use it.



          class math::big_uint



          I'm not convinced that double_block_type ought to be public.



          I'd prefer constraints to static_assert for the template constructors. For example:



                  template<class uint_t, typename = std::enable_if_t<utils::is_uint_v<uint_t>>>
          explicit big_uint(uint_t n);


          with



              template<class block_t>
          template<class uint_t, typename>
          big_uint<block_t>::big_uint(uint_t n):
          big_uint()
          {


          I'm not convinced that this should be explicit - it's a natural, expected conversion. That said, allowing it to be an implicit conversion wouldn't allow us to reduce the overloads of the binary operators, given that they are all template functions.



          Consider adding an overload that accepts block_type:



              template<class block_t>
          big_uint<block_t>::big_uint(block_t n)
          {
          if (n)
          m_data.push_back(n);
          }


          With that in place, the template constructor could be constrained further, accepting only wider types, and letting the block_t constructor be used for narrower types - which can be achieved by adding && !std::is_assignable_v<block_type, uint_t> to the std::enable_if condition.



          It might be useful to also have a converting constructor from big_uint with different block type.



          Missing functionality



          For RSA operations, you'll want to add a modular exponentiation function. That's fairly easily done using the existing functions, but may be more efficient with a modular multiply.






          share|improve this answer























          • Thanks. This is really helpful. Is there a particular reason for preferring enable_if for the constructor? static_assert seemed like it would give a more understandable error for what might be a common mistake (big_uint_32(23) vs big_uint_32(23u)).
            – user673679
            2 days ago










          • Btw, It seems that bit_or_assign and bit_xor_assign need to use b as the main input sequence for std::transform, because b can be shorter than a. It also looks like I forgot to trim the result after bit_and_assign. :S
            – user673679
            2 days ago










          • I suggest the enable_if because it completely removes the invalid versions from participating in overload resolution (which might make the compiler's job easier). I agree that it's nice to be able to offer a better error message!
            – Toby Speight
            2 days ago










          • Yes, you're right about the (x)or_assign() - I copied from the and method without thinking! Now fixed; thanks.
            – Toby Speight
            2 days ago












          • Yep, the truncation is correct. But if, e.g., a contains blocks 1, 0, 0 and b is 1, 0 then we truncate and do the & and get a result of 0, 0. I've been trying to ensure that the last block is always non-zero, so these empty blocks need to be erased by calling utils::trim. I've been wondering if this "no trailing empty blocks" policy is actually a good idea though, when it comes to encoding byte strings for encryption I presumably need to preserve null values.
            – user673679
            yesterday











          Your Answer





          StackExchange.ifUsing("editor", function () {
          return StackExchange.using("mathjaxEditing", function () {
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          });
          });
          }, "mathjax-editing");

          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "196"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














           

          draft saved


          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f207918%2fc-big-unsigned-integer-class%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes








          up vote
          1
          down vote



          accepted










          Nice code; I didn't find any serious issues, so my comments are mostly limited to mere nitpicking.



          namespace math::util



          In has_extra_empty_blocks(), there's no need to continue searching if we don't have a match to begin with:




                  template<class block_t>
          bool has_extra_empty_blocks(big_uint<block_t> const& a)
          {
          return
          (std::find_if(a.data().rbegin(), a.data().rend(),
          (block_t b) { return b != block_t{ 0 }; }).base() !=
          a.data().end());
          }



          (Also, it's simpler to just compare the reverse iterator against a.data().rbegin() than to convert to forward iterator). We just want to check that the last element (if there is one) is zero:



                  template<class block_t>
          bool has_extra_empty_blocks(big_uint<block_t> const& a)
          {
          return !a.empty() && a.back() == block_t{0};
          }


          That looks much more readable, as well as being more efficient.



          I think that has_extra_empty_blocks() and trim() would probably make more sense as member functions of big_uint.



          namespace math::ops



          These functions all look like they should be members of big_uint (generally, inlined into the respective operators); that should obviate the need to expose data() publicly.



          Loops like this:




                      for (auto i = std::size_t{ 0 }; i != min_size; ++i)
          a.data()[i] &= b.data()[i];



          look like they would be more naturally written using std::transform():



                  template<class block_t>
          void bit_and_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
          {
          if (a.data().size() > b.data().size())
          a.data().resize(b.data().size());

          std::transform(a.data().begin(), a.data().end(), b.data().begin(),
          a.data().begin(), std::bit_and<block_t>{});
          }

          template<class block_t>
          void bit_or_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
          {
          if (a.data().size() < b.data().size())
          a.data().resize(b.data().size());

          std::transform(b.data().begin(), b.data().end(), a.data().begin(),
          a.data().begin(), std::bit_or<block_t>{});
          }

          template<class block_t>
          void bit_xor_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
          {
          if (a.data().size() < b.data().size())
          a.data().resize(b.data().size());

          std::transform(b.data().begin(), b.data().end(), a.data().begin(),
          a.data().begin(), std::bit_xor<block_t>{});

          utils::trim(a);
          }


          The shift operators repeat the test of n == 0 before and after shifting by units of block_digits. This could be simplified:



                      if (a.is_zero())
          return;

          if (n >= block_digits)
          {
          ... shift whole blocks
          }

          if (n == bit_index_t{ 0 })
          return;


          Addition and subtraction don't need twice the width of block_t. Carry is only ever one bit, and unsigned overflow is well-defined:



                      for (auto i = std::size_t{ 0 }; i != min_size; ++i)
          {
          bool carry_out = a_data[i] >= ~b_data[i] + !carry;
          a_data[i] += b_data[i] + carry;
          carry = carry_out;
          }


          It is possible to implement multiplication without double_block_t, but I'm not sure that it's worth the effort. Something to consider, perhaps only when double_block_t isn't defined?



          Both div_assign and mod_assign declare double_block_t, but never use it.



          class math::big_uint



          I'm not convinced that double_block_type ought to be public.



          I'd prefer constraints to static_assert for the template constructors. For example:



                  template<class uint_t, typename = std::enable_if_t<utils::is_uint_v<uint_t>>>
          explicit big_uint(uint_t n);


          with



              template<class block_t>
          template<class uint_t, typename>
          big_uint<block_t>::big_uint(uint_t n):
          big_uint()
          {


          I'm not convinced that this should be explicit - it's a natural, expected conversion. That said, allowing it to be an implicit conversion wouldn't allow us to reduce the overloads of the binary operators, given that they are all template functions.



          Consider adding an overload that accepts block_type:



              template<class block_t>
          big_uint<block_t>::big_uint(block_t n)
          {
          if (n)
          m_data.push_back(n);
          }


          With that in place, the template constructor could be constrained further, accepting only wider types, and letting the block_t constructor be used for narrower types - which can be achieved by adding && !std::is_assignable_v<block_type, uint_t> to the std::enable_if condition.



          It might be useful to also have a converting constructor from big_uint with different block type.



          Missing functionality



          For RSA operations, you'll want to add a modular exponentiation function. That's fairly easily done using the existing functions, but may be more efficient with a modular multiply.






          share|improve this answer























          • Thanks. This is really helpful. Is there a particular reason for preferring enable_if for the constructor? static_assert seemed like it would give a more understandable error for what might be a common mistake (big_uint_32(23) vs big_uint_32(23u)).
            – user673679
            2 days ago










          • Btw, It seems that bit_or_assign and bit_xor_assign need to use b as the main input sequence for std::transform, because b can be shorter than a. It also looks like I forgot to trim the result after bit_and_assign. :S
            – user673679
            2 days ago










          • I suggest the enable_if because it completely removes the invalid versions from participating in overload resolution (which might make the compiler's job easier). I agree that it's nice to be able to offer a better error message!
            – Toby Speight
            2 days ago










          • Yes, you're right about the (x)or_assign() - I copied from the and method without thinking! Now fixed; thanks.
            – Toby Speight
            2 days ago












          • Yep, the truncation is correct. But if, e.g., a contains blocks 1, 0, 0 and b is 1, 0 then we truncate and do the & and get a result of 0, 0. I've been trying to ensure that the last block is always non-zero, so these empty blocks need to be erased by calling utils::trim. I've been wondering if this "no trailing empty blocks" policy is actually a good idea though, when it comes to encoding byte strings for encryption I presumably need to preserve null values.
            – user673679
            yesterday















          up vote
          1
          down vote



          accepted










          Nice code; I didn't find any serious issues, so my comments are mostly limited to mere nitpicking.



          namespace math::util



          In has_extra_empty_blocks(), there's no need to continue searching if we don't have a match to begin with:




                  template<class block_t>
          bool has_extra_empty_blocks(big_uint<block_t> const& a)
          {
          return
          (std::find_if(a.data().rbegin(), a.data().rend(),
          (block_t b) { return b != block_t{ 0 }; }).base() !=
          a.data().end());
          }



          (Also, it's simpler to just compare the reverse iterator against a.data().rbegin() than to convert to forward iterator). We just want to check that the last element (if there is one) is zero:



                  template<class block_t>
          bool has_extra_empty_blocks(big_uint<block_t> const& a)
          {
          return !a.empty() && a.back() == block_t{0};
          }


          That looks much more readable, as well as being more efficient.



          I think that has_extra_empty_blocks() and trim() would probably make more sense as member functions of big_uint.



          namespace math::ops



          These functions all look like they should be members of big_uint (generally, inlined into the respective operators); that should obviate the need to expose data() publicly.



          Loops like this:




                      for (auto i = std::size_t{ 0 }; i != min_size; ++i)
          a.data()[i] &= b.data()[i];



          look like they would be more naturally written using std::transform():



                  template<class block_t>
          void bit_and_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
          {
          if (a.data().size() > b.data().size())
          a.data().resize(b.data().size());

          std::transform(a.data().begin(), a.data().end(), b.data().begin(),
          a.data().begin(), std::bit_and<block_t>{});
          }

          template<class block_t>
          void bit_or_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
          {
          if (a.data().size() < b.data().size())
          a.data().resize(b.data().size());

          std::transform(b.data().begin(), b.data().end(), a.data().begin(),
          a.data().begin(), std::bit_or<block_t>{});
          }

          template<class block_t>
          void bit_xor_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
          {
          if (a.data().size() < b.data().size())
          a.data().resize(b.data().size());

          std::transform(b.data().begin(), b.data().end(), a.data().begin(),
          a.data().begin(), std::bit_xor<block_t>{});

          utils::trim(a);
          }


          The shift operators repeat the test of n == 0 before and after shifting by units of block_digits. This could be simplified:



                      if (a.is_zero())
          return;

          if (n >= block_digits)
          {
          ... shift whole blocks
          }

          if (n == bit_index_t{ 0 })
          return;


          Addition and subtraction don't need twice the width of block_t. Carry is only ever one bit, and unsigned overflow is well-defined:



                      for (auto i = std::size_t{ 0 }; i != min_size; ++i)
          {
          bool carry_out = a_data[i] >= ~b_data[i] + !carry;
          a_data[i] += b_data[i] + carry;
          carry = carry_out;
          }


          It is possible to implement multiplication without double_block_t, but I'm not sure that it's worth the effort. Something to consider, perhaps only when double_block_t isn't defined?



          Both div_assign and mod_assign declare double_block_t, but never use it.



          class math::big_uint



          I'm not convinced that double_block_type ought to be public.



          I'd prefer constraints to static_assert for the template constructors. For example:



                  template<class uint_t, typename = std::enable_if_t<utils::is_uint_v<uint_t>>>
          explicit big_uint(uint_t n);


          with



              template<class block_t>
          template<class uint_t, typename>
          big_uint<block_t>::big_uint(uint_t n):
          big_uint()
          {


          I'm not convinced that this should be explicit - it's a natural, expected conversion. That said, allowing it to be an implicit conversion wouldn't allow us to reduce the overloads of the binary operators, given that they are all template functions.



          Consider adding an overload that accepts block_type:



              template<class block_t>
          big_uint<block_t>::big_uint(block_t n)
          {
          if (n)
          m_data.push_back(n);
          }


          With that in place, the template constructor could be constrained further, accepting only wider types, and letting the block_t constructor be used for narrower types - which can be achieved by adding && !std::is_assignable_v<block_type, uint_t> to the std::enable_if condition.



          It might be useful to also have a converting constructor from big_uint with different block type.



          Missing functionality



          For RSA operations, you'll want to add a modular exponentiation function. That's fairly easily done using the existing functions, but may be more efficient with a modular multiply.






          share|improve this answer























          • Thanks. This is really helpful. Is there a particular reason for preferring enable_if for the constructor? static_assert seemed like it would give a more understandable error for what might be a common mistake (big_uint_32(23) vs big_uint_32(23u)).
            – user673679
            2 days ago










          • Btw, It seems that bit_or_assign and bit_xor_assign need to use b as the main input sequence for std::transform, because b can be shorter than a. It also looks like I forgot to trim the result after bit_and_assign. :S
            – user673679
            2 days ago










          • I suggest the enable_if because it completely removes the invalid versions from participating in overload resolution (which might make the compiler's job easier). I agree that it's nice to be able to offer a better error message!
            – Toby Speight
            2 days ago










          • Yes, you're right about the (x)or_assign() - I copied from the and method without thinking! Now fixed; thanks.
            – Toby Speight
            2 days ago












          • Yep, the truncation is correct. But if, e.g., a contains blocks 1, 0, 0 and b is 1, 0 then we truncate and do the & and get a result of 0, 0. I've been trying to ensure that the last block is always non-zero, so these empty blocks need to be erased by calling utils::trim. I've been wondering if this "no trailing empty blocks" policy is actually a good idea though, when it comes to encoding byte strings for encryption I presumably need to preserve null values.
            – user673679
            yesterday













          up vote
          1
          down vote



          accepted







          up vote
          1
          down vote



          accepted






          Nice code; I didn't find any serious issues, so my comments are mostly limited to mere nitpicking.



          namespace math::util



          In has_extra_empty_blocks(), there's no need to continue searching if we don't have a match to begin with:




                  template<class block_t>
          bool has_extra_empty_blocks(big_uint<block_t> const& a)
          {
          return
          (std::find_if(a.data().rbegin(), a.data().rend(),
          (block_t b) { return b != block_t{ 0 }; }).base() !=
          a.data().end());
          }



          (Also, it's simpler to just compare the reverse iterator against a.data().rbegin() than to convert to forward iterator). We just want to check that the last element (if there is one) is zero:



                  template<class block_t>
          bool has_extra_empty_blocks(big_uint<block_t> const& a)
          {
          return !a.empty() && a.back() == block_t{0};
          }


          That looks much more readable, as well as being more efficient.



          I think that has_extra_empty_blocks() and trim() would probably make more sense as member functions of big_uint.



          namespace math::ops



          These functions all look like they should be members of big_uint (generally, inlined into the respective operators); that should obviate the need to expose data() publicly.



          Loops like this:




                      for (auto i = std::size_t{ 0 }; i != min_size; ++i)
          a.data()[i] &= b.data()[i];



          look like they would be more naturally written using std::transform():



                  template<class block_t>
          void bit_and_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
          {
          if (a.data().size() > b.data().size())
          a.data().resize(b.data().size());

          std::transform(a.data().begin(), a.data().end(), b.data().begin(),
          a.data().begin(), std::bit_and<block_t>{});
          }

          template<class block_t>
          void bit_or_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
          {
          if (a.data().size() < b.data().size())
          a.data().resize(b.data().size());

          std::transform(b.data().begin(), b.data().end(), a.data().begin(),
          a.data().begin(), std::bit_or<block_t>{});
          }

          template<class block_t>
          void bit_xor_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
          {
          if (a.data().size() < b.data().size())
          a.data().resize(b.data().size());

          std::transform(b.data().begin(), b.data().end(), a.data().begin(),
          a.data().begin(), std::bit_xor<block_t>{});

          utils::trim(a);
          }


          The shift operators repeat the test of n == 0 before and after shifting by units of block_digits. This could be simplified:



                      if (a.is_zero())
          return;

          if (n >= block_digits)
          {
          ... shift whole blocks
          }

          if (n == bit_index_t{ 0 })
          return;


          Addition and subtraction don't need twice the width of block_t. Carry is only ever one bit, and unsigned overflow is well-defined:



                      for (auto i = std::size_t{ 0 }; i != min_size; ++i)
          {
          bool carry_out = a_data[i] >= ~b_data[i] + !carry;
          a_data[i] += b_data[i] + carry;
          carry = carry_out;
          }


          It is possible to implement multiplication without double_block_t, but I'm not sure that it's worth the effort. Something to consider, perhaps only when double_block_t isn't defined?



          Both div_assign and mod_assign declare double_block_t, but never use it.



          class math::big_uint



          I'm not convinced that double_block_type ought to be public.



          I'd prefer constraints to static_assert for the template constructors. For example:



                  template<class uint_t, typename = std::enable_if_t<utils::is_uint_v<uint_t>>>
          explicit big_uint(uint_t n);


          with



              template<class block_t>
          template<class uint_t, typename>
          big_uint<block_t>::big_uint(uint_t n):
          big_uint()
          {


          I'm not convinced that this should be explicit - it's a natural, expected conversion. That said, allowing it to be an implicit conversion wouldn't allow us to reduce the overloads of the binary operators, given that they are all template functions.



          Consider adding an overload that accepts block_type:



              template<class block_t>
          big_uint<block_t>::big_uint(block_t n)
          {
          if (n)
          m_data.push_back(n);
          }


          With that in place, the template constructor could be constrained further, accepting only wider types, and letting the block_t constructor be used for narrower types - which can be achieved by adding && !std::is_assignable_v<block_type, uint_t> to the std::enable_if condition.



          It might be useful to also have a converting constructor from big_uint with different block type.



          Missing functionality



          For RSA operations, you'll want to add a modular exponentiation function. That's fairly easily done using the existing functions, but may be more efficient with a modular multiply.






          share|improve this answer














          Nice code; I didn't find any serious issues, so my comments are mostly limited to mere nitpicking.



          namespace math::util



          In has_extra_empty_blocks(), there's no need to continue searching if we don't have a match to begin with:




                  template<class block_t>
          bool has_extra_empty_blocks(big_uint<block_t> const& a)
          {
          return
          (std::find_if(a.data().rbegin(), a.data().rend(),
          (block_t b) { return b != block_t{ 0 }; }).base() !=
          a.data().end());
          }



          (Also, it's simpler to just compare the reverse iterator against a.data().rbegin() than to convert to forward iterator). We just want to check that the last element (if there is one) is zero:



                  template<class block_t>
          bool has_extra_empty_blocks(big_uint<block_t> const& a)
          {
          return !a.empty() && a.back() == block_t{0};
          }


          That looks much more readable, as well as being more efficient.



          I think that has_extra_empty_blocks() and trim() would probably make more sense as member functions of big_uint.



          namespace math::ops



          These functions all look like they should be members of big_uint (generally, inlined into the respective operators); that should obviate the need to expose data() publicly.



          Loops like this:




                      for (auto i = std::size_t{ 0 }; i != min_size; ++i)
          a.data()[i] &= b.data()[i];



          look like they would be more naturally written using std::transform():



                  template<class block_t>
          void bit_and_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
          {
          if (a.data().size() > b.data().size())
          a.data().resize(b.data().size());

          std::transform(a.data().begin(), a.data().end(), b.data().begin(),
          a.data().begin(), std::bit_and<block_t>{});
          }

          template<class block_t>
          void bit_or_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
          {
          if (a.data().size() < b.data().size())
          a.data().resize(b.data().size());

          std::transform(b.data().begin(), b.data().end(), a.data().begin(),
          a.data().begin(), std::bit_or<block_t>{});
          }

          template<class block_t>
          void bit_xor_assign(big_uint<block_t>& a, big_uint<block_t> const& b)
          {
          if (a.data().size() < b.data().size())
          a.data().resize(b.data().size());

          std::transform(b.data().begin(), b.data().end(), a.data().begin(),
          a.data().begin(), std::bit_xor<block_t>{});

          utils::trim(a);
          }


          The shift operators repeat the test of n == 0 before and after shifting by units of block_digits. This could be simplified:



                      if (a.is_zero())
          return;

          if (n >= block_digits)
          {
          ... shift whole blocks
          }

          if (n == bit_index_t{ 0 })
          return;


          Addition and subtraction don't need twice the width of block_t. Carry is only ever one bit, and unsigned overflow is well-defined:



                      for (auto i = std::size_t{ 0 }; i != min_size; ++i)
          {
          bool carry_out = a_data[i] >= ~b_data[i] + !carry;
          a_data[i] += b_data[i] + carry;
          carry = carry_out;
          }


          It is possible to implement multiplication without double_block_t, but I'm not sure that it's worth the effort. Something to consider, perhaps only when double_block_t isn't defined?



          Both div_assign and mod_assign declare double_block_t, but never use it.



          class math::big_uint



          I'm not convinced that double_block_type ought to be public.



          I'd prefer constraints to static_assert for the template constructors. For example:



                  template<class uint_t, typename = std::enable_if_t<utils::is_uint_v<uint_t>>>
          explicit big_uint(uint_t n);


          with



              template<class block_t>
          template<class uint_t, typename>
          big_uint<block_t>::big_uint(uint_t n):
          big_uint()
          {


          I'm not convinced that this should be explicit - it's a natural, expected conversion. That said, allowing it to be an implicit conversion wouldn't allow us to reduce the overloads of the binary operators, given that they are all template functions.



          Consider adding an overload that accepts block_type:



              template<class block_t>
          big_uint<block_t>::big_uint(block_t n)
          {
          if (n)
          m_data.push_back(n);
          }


          With that in place, the template constructor could be constrained further, accepting only wider types, and letting the block_t constructor be used for narrower types - which can be achieved by adding && !std::is_assignable_v<block_type, uint_t> to the std::enable_if condition.



          It might be useful to also have a converting constructor from big_uint with different block type.



          Missing functionality



          For RSA operations, you'll want to add a modular exponentiation function. That's fairly easily done using the existing functions, but may be more efficient with a modular multiply.







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited yesterday

























          answered 2 days ago









          Toby Speight

          22.2k536108




          22.2k536108












          • Thanks. This is really helpful. Is there a particular reason for preferring enable_if for the constructor? static_assert seemed like it would give a more understandable error for what might be a common mistake (big_uint_32(23) vs big_uint_32(23u)).
            – user673679
            2 days ago










          • Btw, It seems that bit_or_assign and bit_xor_assign need to use b as the main input sequence for std::transform, because b can be shorter than a. It also looks like I forgot to trim the result after bit_and_assign. :S
            – user673679
            2 days ago










          • I suggest the enable_if because it completely removes the invalid versions from participating in overload resolution (which might make the compiler's job easier). I agree that it's nice to be able to offer a better error message!
            – Toby Speight
            2 days ago










          • Yes, you're right about the (x)or_assign() - I copied from the and method without thinking! Now fixed; thanks.
            – Toby Speight
            2 days ago












          • Yep, the truncation is correct. But if, e.g., a contains blocks 1, 0, 0 and b is 1, 0 then we truncate and do the & and get a result of 0, 0. I've been trying to ensure that the last block is always non-zero, so these empty blocks need to be erased by calling utils::trim. I've been wondering if this "no trailing empty blocks" policy is actually a good idea though, when it comes to encoding byte strings for encryption I presumably need to preserve null values.
            – user673679
            yesterday


















          • Thanks. This is really helpful. Is there a particular reason for preferring enable_if for the constructor? static_assert seemed like it would give a more understandable error for what might be a common mistake (big_uint_32(23) vs big_uint_32(23u)).
            – user673679
            2 days ago










          • Btw, It seems that bit_or_assign and bit_xor_assign need to use b as the main input sequence for std::transform, because b can be shorter than a. It also looks like I forgot to trim the result after bit_and_assign. :S
            – user673679
            2 days ago










          • I suggest the enable_if because it completely removes the invalid versions from participating in overload resolution (which might make the compiler's job easier). I agree that it's nice to be able to offer a better error message!
            – Toby Speight
            2 days ago










          • Yes, you're right about the (x)or_assign() - I copied from the and method without thinking! Now fixed; thanks.
            – Toby Speight
            2 days ago












          • Yep, the truncation is correct. But if, e.g., a contains blocks 1, 0, 0 and b is 1, 0 then we truncate and do the & and get a result of 0, 0. I've been trying to ensure that the last block is always non-zero, so these empty blocks need to be erased by calling utils::trim. I've been wondering if this "no trailing empty blocks" policy is actually a good idea though, when it comes to encoding byte strings for encryption I presumably need to preserve null values.
            – user673679
            yesterday
















          Thanks. This is really helpful. Is there a particular reason for preferring enable_if for the constructor? static_assert seemed like it would give a more understandable error for what might be a common mistake (big_uint_32(23) vs big_uint_32(23u)).
          – user673679
          2 days ago




          Thanks. This is really helpful. Is there a particular reason for preferring enable_if for the constructor? static_assert seemed like it would give a more understandable error for what might be a common mistake (big_uint_32(23) vs big_uint_32(23u)).
          – user673679
          2 days ago












          Btw, It seems that bit_or_assign and bit_xor_assign need to use b as the main input sequence for std::transform, because b can be shorter than a. It also looks like I forgot to trim the result after bit_and_assign. :S
          – user673679
          2 days ago




          Btw, It seems that bit_or_assign and bit_xor_assign need to use b as the main input sequence for std::transform, because b can be shorter than a. It also looks like I forgot to trim the result after bit_and_assign. :S
          – user673679
          2 days ago












          I suggest the enable_if because it completely removes the invalid versions from participating in overload resolution (which might make the compiler's job easier). I agree that it's nice to be able to offer a better error message!
          – Toby Speight
          2 days ago




          I suggest the enable_if because it completely removes the invalid versions from participating in overload resolution (which might make the compiler's job easier). I agree that it's nice to be able to offer a better error message!
          – Toby Speight
          2 days ago












          Yes, you're right about the (x)or_assign() - I copied from the and method without thinking! Now fixed; thanks.
          – Toby Speight
          2 days ago






          Yes, you're right about the (x)or_assign() - I copied from the and method without thinking! Now fixed; thanks.
          – Toby Speight
          2 days ago














          Yep, the truncation is correct. But if, e.g., a contains blocks 1, 0, 0 and b is 1, 0 then we truncate and do the & and get a result of 0, 0. I've been trying to ensure that the last block is always non-zero, so these empty blocks need to be erased by calling utils::trim. I've been wondering if this "no trailing empty blocks" policy is actually a good idea though, when it comes to encoding byte strings for encryption I presumably need to preserve null values.
          – user673679
          yesterday




          Yep, the truncation is correct. But if, e.g., a contains blocks 1, 0, 0 and b is 1, 0 then we truncate and do the & and get a result of 0, 0. I've been trying to ensure that the last block is always non-zero, so these empty blocks need to be erased by calling utils::trim. I've been wondering if this "no trailing empty blocks" policy is actually a good idea though, when it comes to encoding byte strings for encryption I presumably need to preserve null values.
          – user673679
          yesterday


















           

          draft saved


          draft discarded



















































           


          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f207918%2fc-big-unsigned-integer-class%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          Morgemoulin

          Scott Moir

          Souastre