psemek/libs/util/include/psemek/util/big_int.hpp
lisyarus 2c3565df61 32-bit compilation fixes:
* Use uint64_t instead of size_t as hash return value

 * Expect alignof(uint64_t) <= 8 instead of == 8
2025-01-25 20:35:37 +03:00

197 lines
4.1 KiB
C++

#pragma once
#include <psemek/util/span.hpp>
#include <psemek/util/range.hpp>
#include <cstdint>
#include <cstddef>
#include <memory>
#include <concepts>
namespace psemek::util
{
struct big_int
{
using digit = std::uint32_t;
big_int() = default;
template <std::integral T>
big_int(T value);
big_int(big_int const &) = delete;
big_int(big_int && other);
static big_int from_digits(std::unique_ptr<digit[]> digits, std::uint32_t size, bool negative = false);
template <typename Iterator>
static big_int from_digits(Iterator begin, Iterator end, bool negative = false);
template <typename Container>
static big_int from_digits(Container && container, bool negative = false);
big_int & operator = (big_int const &) = delete;
big_int & operator = (big_int && other);
util::span<digit> digits();
util::span<digit const> digits() const;
bool negative() const;
private:
static constexpr std::uint32_t sign_bit = std::uint32_t(1) << 31;
static constexpr std::uint32_t digit_size = sizeof(digit);
static constexpr std::uint32_t digit_bits = digit_size * 8;
static constexpr std::uint32_t digit_mask = digit(-1);
std::unique_ptr<digit[]> digits_ = nullptr;
// Highest bit of size stores the overall big int sign
std::uint32_t size_ = 0;
std::uint32_t capacity_ = 0;
void set_negative(bool negative)
{
size_ = size() | (negative ? sign_bit : 0);
}
std::uint32_t size() const
{
return size_ & (~sign_bit);
}
void set_size(std::uint32_t new_size)
{
size_ = new_size | (size_ & sign_bit);
}
void canonicalize();
void ensure_capacity(std::uint32_t new_capacity);
template <std::integral T>
static T shift_by_digit(T value);
template <std::integral T>
static std::uint32_t size_for(T value);
void add_positive(span<digit const> digits);
};
template <std::integral T>
big_int::big_int(T value)
{
auto size = size_for(value);
ensure_capacity(size);
set_size(size);
set_negative(value < 0);
for (digit & d : digits())
{
d = value & digit_mask;
value = shift_by_digit(value);
}
if (negative())
{
for (digit & d : digits())
d = ~d;
digit const one = 1;
add_positive(make_singleton_span(one));
}
}
inline big_int::big_int(big_int && other)
: digits_(std::move(other.digits_))
, size_(other.size_)
, capacity_(other.capacity_)
{
other.size_ = 0;
other.capacity_ = 0;
}
inline big_int big_int::from_digits(std::unique_ptr<digit[]> digits, std::uint32_t size, bool negative)
{
big_int result;
result.digits_ = std::move(digits);
result.size_ = size;
result.capacity_ = size;
result.set_negative(negative);
result.canonicalize();
return result;
}
template <typename Iterator>
big_int big_int::from_digits(Iterator begin, Iterator end, bool negative)
{
auto size = std::distance(begin, end);
auto digits = std::make_unique<digit[]>(size);
std::copy(begin, end, digits.get());
return from_digits(std::move(digits), size, negative);
}
template <typename Container>
big_int big_int::from_digits(Container && container, bool negative)
{
return from_digits(util::xbegin(container), util::xend(container), negative);
}
inline util::span<big_int::digit> big_int::digits()
{
return {digits_.get(), size()};
}
inline util::span<big_int::digit const> big_int::digits() const
{
return {digits_.get(), size()};
}
inline bool big_int::negative() const
{
return (size_ & sign_bit) == sign_bit;
}
template <std::integral T>
T big_int::shift_by_digit(T value)
{
if constexpr (sizeof(value) <= digit_size)
{
if (value < 0)
return -1;
else
return 0;
}
else
{
return value >> digit_bits;
}
}
template <std::integral T>
std::uint32_t big_int::size_for(T value)
{
std::uint32_t result_bits = 0;
if (value >= 0)
{
while (value > 0)
{
value = shift_by_digit(value);
result_bits += digit_bits;
}
}
else
{
result_bits = 1;
while ((value >> (digit_bits - 1)) != T(-1))
{
value = shift_by_digit(value);
result_bits += digit_bits;
}
}
return (result_bits + (digit_bits - 1)) / digit_bits;
}
}