This commit is contained in:
tovjemam 2025-11-22 19:36:26 +01:00
parent ac10af30da
commit 08ea929084
7 changed files with 558 additions and 9 deletions

View File

@ -4,3 +4,5 @@ PointerAlignment: Left
IndentWidth: 4 # spaces per indent level
TabWidth: 4 # width of a tab character
UseTab: Never # options: Never, ForIndentation, Alwayss
BreakTemplateDeclarations: Yes
AllowShortFunctionsOnASingleLine: Inline

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
.vscode/
.vs/
build/

View File

@ -1,31 +1,84 @@
#include <iostream>
#include "mp.hpp"
//#include "mp.hpp"
#include "mp/int.hpp"
#include "mp/storage.hpp"
#include "mp/math.hpp"
template <class T>
static void PrintInt(const char* name, const T& val)
{
std::cout << name << " = " << mp::ToHexString(val) << std::endl;
std::cout << name << " = " << mp::to_hex_string(val) << std::endl;
}
int main()
{
// mp::Int a{0xDEADBEEFDEADF154, 0x0123456789ABCDEF, 0x1111222233334444};
mp::Int<4> a{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF};
mp::Int<5> b{0x55};
mp::Int<32> a{0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF};
mp::Int<1> b{0x55};
PrintInt("a", a);
PrintInt("b", b);
auto c = a + b;
//auto c = binary_opa, b);
PrintInt("c", c);
a += mp::Int<1>{1};
PrintInt("a", a);
//a += mp::Int<1>{1};
//PrintInt("a", a);
a -= mp::Int<1>{1};
PrintInt("a", a);
//a -= mp::Int<1>{1};
//PrintInt("a", a);
std::cout << mp::Int<16>::LAST_ELEM_MASK << std::endl;
std::cout << mp::Int<17>::LAST_ELEM_MASK << std::endl;
std::cout << sizeof(mp::Int<1>) << std::endl;
std::cout << sizeof(mp::Int<2>) << std::endl;
std::cout << sizeof(mp::Int<3>) << std::endl;
std::cout << sizeof(mp::Int<4>) << std::endl;
std::cout << sizeof(mp::Int<5>) << std::endl;
std::cout << sizeof(mp::Int<6>) << std::endl;
std::cout << sizeof(mp::Int<7>) << std::endl;
std::cout << sizeof(mp::Int<8>) << std::endl;
std::cout << sizeof(mp::Int<9>) << std::endl;
std::cout << sizeof(mp::Int<10>) << std::endl;
std::cout << sizeof(mp::Int<20>) << std::endl;
std::cout << sizeof(mp::Int<30>) << std::endl;
std::cout << sizeof(mp::Int<40>) << std::endl;
std::cout << sizeof(mp::Int<50>) << std::endl;
std::cout << sizeof(mp::Int<60>) << std::endl;
std::cout << sizeof(mp::Int<70>) << std::endl;
std::cout << sizeof(mp::Int<80>) << std::endl;
std::cout << sizeof(mp::Int<90>) << std::endl;
std::cout << sizeof(mp::Int<100>) << std::endl;
std::cout << sizeof(mp::Int<110>) << std::endl;
std::cout << sizeof(mp::Int<120>) << std::endl;
std::cout << sizeof(mp::Int<130>) << std::endl;
std::cout << sizeof(mp::Int<140>) << std::endl;
std::cout << sizeof(mp::Int<150>) << std::endl;
std::cout << sizeof(mp::Int<160>) << std::endl;
{
mp::Int<1024> acc{1};
try
{
while (true)
{
acc *= mp::Int<1>{10};
PrintInt("acc", acc);
}
}
catch (const mp::OverflowErrorOf<decltype(acc), mp::Int<1>>& e)
{
std::cout << "overflow" << std::endl;
PrintInt("value", e.value());
}
catch (const std::exception& e)
{
std::cout << "error: " << e.what() << std::endl;
}
}
}

97
mp/int.hpp Normal file
View File

@ -0,0 +1,97 @@
#pragma once
#include <ranges>
#include "utils.hpp"
#include "storage.hpp"
namespace mp
{
template <ElementSuitable TElem, size_t MaxBytes>
class BasicInt
{
public:
using ElementType = TElem;
constexpr static size_t MAX_BYTES = MaxBytes;
constexpr static size_t ELEMENT_BYTES = sizeof(TElem);
constexpr static size_t MAX_ELEMS = (MAX_BYTES + ELEMENT_BYTES - 1) / ELEMENT_BYTES;
constexpr static TElem LAST_ELEM_MASK = calculate_last_elem_mask<TElem, MAX_BYTES>();
BasicInt() = default;
BasicInt(std::initializer_list<ElementType> init)
{
for (size_t i = 0; i < init.size(); ++i)
{
set(i, *(init.begin() + i));
}
}
TElem& operator[](size_t index) { return m_data[index]; }
const TElem& operator[](size_t index) const { return m_data[index]; }
void resize(size_t new_size)
{
m_data.resize(new_size);
}
void zero() { m_data.clear(); }
bool try_set(size_t idx, TElem value)
{
if (idx >= m_data.size())
{
if (value == 0)
{
return true;
}
if (idx >= MAX_ELEMS)
{
return false;
}
m_data.resize(idx + 1);
}
m_data[idx] = value;
if (idx == MAX_ELEMS - 1 && value > LAST_ELEM_MASK)
{
m_data[idx] &= LAST_ELEM_MASK;
return false;
}
return true;
}
void set(size_t idx, TElem value)
{
if (!try_set(idx, value))
{
throw std::out_of_range("Value exceeds maximum size");
}
}
TElem get(size_t idx) const
{
if (idx >= m_data.size())
{
return TElem{0};
}
return m_data[idx];
}
//std::span<TElem> data() { return m_data; }
size_t size_elems() const { return m_data.size(); }
private:
Container<TElem, MAX_ELEMS> m_data;
};
template<size_t MaxBytes>
using Int = BasicInt<LongestElementSuitableType, MaxBytes>;
} // namespace mp

172
mp/math.hpp Normal file
View File

@ -0,0 +1,172 @@
#pragma once
#include "int.hpp"
#include "storage.hpp"
#include "utils.hpp"
namespace mp
{
template <size_t SizeA, size_t SizeB>
constexpr size_t ResultMaxSize = std::max(SizeA, SizeB);
template <AnyInt TLhs, AnyInt TRhs>
requires ElementTypesMatch<TLhs, TRhs>
using OpResult = BasicInt<typename TLhs::ElementType, ResultMaxSize<TLhs::MAX_BYTES, TRhs::MAX_BYTES>>;
template <AnyInt T>
class OverflowError : public std::runtime_error
{
public:
OverflowError(T&& value) : std::runtime_error("Overflow"), m_value(std::move(value)) {}
const T& value() const { return m_value; }
private:
T m_value;
};
template <AnyInt TLhs, AnyInt TRhs>
using OverflowErrorOf = OverflowError<OpResult<TLhs, TRhs>>;
struct Addition
{
template <AnyInt TLhs, AnyInt TRhs, AnyInt TRes>
requires ElementTypesMatch<TLhs, TRes> && ElementTypesMatch<TRhs, TRes>
static void invoke(const TLhs& lhs, const TRhs& rhs, TRes& res)
{
using ElementType = typename TRes::ElementType;
res.zero();
ElementType carry = 0;
size_t end = std::max(lhs.size_elems(), rhs.size_elems());
for (size_t i = 0; i < end; ++i)
{
ElementType a = lhs.get(i);
ElementType b = rhs.get(i);
ElementType c = carry + a;
carry = (c < a) ? 1 : 0;
c += b;
if (c < b)
carry = 1;
res.set(i, c);
}
res.set(end, carry);
}
};
struct Multiplication
{
template <AnyInt TLhs, AnyInt TRhs, AnyInt TRes>
requires ElementTypesMatch<TLhs, TRes> && ElementTypesMatch<TRhs, TRes>
static void invoke(const TLhs& lhs, const TRhs& rhs, TRes& res)
{
using ElementType = typename TRes::ElementType;
using DoubleType = DoubleWidthType<ElementType>;
res.zero();
bool overflow = false;
const size_t n = lhs.size_elems();
const size_t m = rhs.size_elems();
for (size_t i = 0; i < n; i++)
{
DoubleType carry = 0;
ElementType a = lhs[i];
for (size_t j = 0; j < m; j++)
{
ElementType b = rhs[j];
DoubleType t = static_cast<DoubleType>(a) * b + res.get(i + j) + carry;
overflow |= !res.try_set(i + j, static_cast<ElementType>(t));
carry = t >> (sizeof(ElementType) * 8);
}
overflow |= !res.try_set(i + m, static_cast<ElementType>(carry));
}
if (overflow)
{
throw std::overflow_error("Multiplication overflow");
}
};
};
// Binary operation
template <typename TOp, AnyInt TLhs, AnyInt TRhs>
requires ElementTypesMatch<TLhs, TRhs>
OpResult<TLhs, TRhs> binary_op(const TLhs& lhs, const TRhs& rhs)
{
OpResult<TLhs, TRhs> res{};
try
{
TOp::invoke(lhs, rhs, res);
}
catch (const std::overflow_error&)
{
throw OverflowErrorOf<TLhs, TRhs>(std::move(res));
}
return res;
}
// Compound assignment binary operation
template <typename TOp, AnyInt TLhs, AnyInt TRhs>
requires ElementTypesMatch<TLhs, TRhs>
TLhs& ca_binary_op(TLhs& lhs, const TRhs& rhs)
{
TLhs res{};
try
{
TOp::invoke(lhs, rhs, res);
}
catch (const std::overflow_error&)
{
throw OverflowErrorOf<TLhs, TRhs>(std::move(res));
}
lhs = std::move(res);
return lhs;
}
// Addition operators
template <AnyInt TLhs, AnyInt TRhs>
requires ElementTypesMatch<TLhs, TRhs>
OpResult<TLhs, TRhs> operator+(const TLhs& lhs, const TRhs& rhs)
{
return binary_op<Addition>(lhs, rhs);
}
template <AnyInt TLhs, AnyInt TRhs>
requires ElementTypesMatch<TLhs, TRhs>
TLhs& operator+=(TLhs& lhs, const TRhs& rhs)
{
return ca_binary_op<Addition>(lhs, rhs);
}
// Multiplication operators
template <AnyInt TLhs, AnyInt TRhs>
requires ElementTypesMatch<TLhs, TRhs>
OpResult<TLhs, TRhs> operator*(const TLhs& lhs, const TRhs& rhs)
{
return binary_op<Multiplication>(lhs, rhs);
}
template <AnyInt TLhs, AnyInt TRhs>
requires ElementTypesMatch<TLhs, TRhs>
TLhs& operator*=(TLhs& lhs, const TRhs& rhs)
{
return ca_binary_op<Multiplication>(lhs, rhs);
}
} // namespace mp

74
mp/storage.hpp Normal file
View File

@ -0,0 +1,74 @@
#pragma once
#include <array>
#include <concepts>
#include <cstddef>
#include <span>
#include <stdexcept>
#include <vector>
#include "utils.hpp"
namespace mp
{
template <typename TElem, size_t MaxSize>
class ArrayContainer
{
public:
ArrayContainer() = default;
TElem& operator[](size_t idx) { return m_data[idx]; }
const TElem& operator[](size_t idx) const { return m_data[idx]; }
size_t size() const { return m_size; }
void clear() { m_size = 0; }
void resize(size_t new_size)
{
if (new_size > MaxSize)
throw std::out_of_range("New size exceeds maximum number of elements");
if (new_size > m_size)
std::fill(m_data.begin() + m_size, m_data.begin() + new_size, TElem{0});
m_size = new_size;
}
private:
size_t m_size = 0;
std::array<TElem, MaxSize> m_data;
};
template <typename TElem, size_t MaxSize>
class VectorContainer
{
public:
VectorContainer() = default;
TElem& operator[](size_t idx) { return m_data[idx]; }
const TElem& operator[](size_t idx) const { return m_data[idx]; }
size_t size() const { return m_data.size(); }
void clear() { m_data.clear(); }
void resize(size_t new_size)
{
if (new_size > MaxSize)
throw std::out_of_range("New size exceeds maximum number of elements");
m_data.resize(new_size, TElem{0});
}
private:
std::vector<TElem> m_data;
};
// threshold for self-contained storage
constexpr size_t MAX_ARRAY_BYTES = 128;
template <typename TElem, size_t MaxSize>
using Container =
std::conditional_t<(sizeof(TElem) * MaxSize > MAX_ARRAY_BYTES), VectorContainer<TElem, MaxSize>, ArrayContainer<TElem, MaxSize>>;
} // namespace mp

150
mp/utils.hpp Normal file
View File

@ -0,0 +1,150 @@
#pragma once
#include <concepts>
#include <cstdint>
#include <type_traits>
namespace mp
{
//======================== DoubleWidthType =======================//
template <typename T>
struct DoubleWidth;
template <>
struct DoubleWidth<uint8_t>
{
using type = uint16_t;
};
template <>
struct DoubleWidth<uint16_t>
{
using type = uint32_t;
};
template <>
struct DoubleWidth<uint32_t>
{
using type = uint64_t;
};
#ifdef __SIZEOF_INT128__
template <>
struct DoubleWidth<uint64_t>
{
using type = __uint128_t;
};
#endif
template <typename T>
using DoubleWidthType = typename DoubleWidth<T>::type;
//======================== HalfWidthType =======================//
template <typename T>
struct HalfWidth;
template <>
struct HalfWidth<uint16_t>
{
using type = uint8_t;
};
template <>
struct HalfWidth<uint32_t>
{
using type = uint16_t;
};
template <>
struct HalfWidth<uint64_t>
{
using type = uint32_t;
};
template <typename T>
using HalfWidthType = typename HalfWidth<T>::type;
//======================== Concepts =======================//
template <typename T>
concept ElementSuitable = std::unsigned_integral<T>;
template <typename T, typename TElem = T::ElementType>
concept AnyInt = requires(T t, TElem a, size_t i) {
{ t[i] } -> std::convertible_to<TElem>;
{ t.set(i, a) };
{ t.try_set(i, a) } -> std::convertible_to<bool>;
{ t.get(i) } -> std::convertible_to<TElem>;
{ t.zero() };
{ t.size_elems() } -> std::convertible_to<size_t>;
{ T::MAX_BYTES } -> std::convertible_to<size_t>;
{ T::MAX_ELEMS } -> std::convertible_to<size_t>;
{ T::ELEMENT_BYTES } -> std::convertible_to<size_t>;
{ T::LAST_ELEM_MASK } -> std::convertible_to<TElem>;
};
//======================== Utils =======================//
#ifdef __SIZEOF_INT128__
using LongestElementSuitableType = uint64_t;
#else
using LongestElementSuitableType = HalfWidthType<size_t>;
#endif
template <AnyInt TA, AnyInt TB>
constexpr bool ElementTypesMatch = std::is_same_v<typename TA::ElementType, typename TB::ElementType>;
template <ElementSuitable TElem, size_t MaxBytes>
constexpr TElem calculate_last_elem_mask()
{
size_t last_elem_bytes = MaxBytes % sizeof(TElem);
TElem mask = 0;
if (last_elem_bytes == 0)
{
return ~mask;
}
for (size_t i = 0; i < last_elem_bytes; ++i)
{
mask <<= 8;
mask |= 0xFF;
}
return mask;
}
inline char hex_digit(uint8_t bits)
{
return bits > 9 ? 'a' + (bits - 10) : '0' + bits;
}
template <AnyInt T>
std::string to_hex_string(const T& number)
{
constexpr size_t ELEMENT_DIGITS = T::ELEMENT_BYTES * 2;
std::string str(number.size_elems() * ELEMENT_DIGITS, '-');
for (size_t elem = 0; elem < number.size_elems(); ++elem)
{
auto v = number[elem];
for (size_t digit = 0; digit < ELEMENT_DIGITS; ++digit)
{
str[str.size() - 1 - (elem * ELEMENT_DIGITS) - digit] = hex_digit((v >> (digit * 4)) & 0xF);
}
}
auto first = str.find_first_not_of('0');
if (first != std::string::npos)
{
return "0x" + str.substr(first);
}
return "0x0";
}
}