// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later // IntBase.hpp // Base definitions for the multi-precision integer class #ifndef SANGI_INT_BASE_HPP #define SANGI_INT_BASE_HPP #include "../../common.hpp" // SANGI_API #include "../../basic_types.hpp" #include "../../numeric_state.hpp" // includes the definition of NumericStateTraits #include "UInt128.hpp" #include "SboWords.hpp" #include #include #include #include #include #include // forward declarations of std::ostream, std::istream #include // C++20 span for zero-copy operations #include // C++20 three-way comparison operator #include // std::pair #include // std::is_integral_v, std::enable_if_t namespace sangi { // Forward declarations class IntOps; class IntSpecialStates; class IntIOUtils; /** * @brief Multi-precision integer class * * A class representing arbitrary-precision integers. Internally, the value is held as * a variable-length array of 64-bit words. */ class SANGI_API Int { public: // No upper bound on word count — arbitrary precision limited only by memory // (SboWords is size_t-based, so there is no type constraint either) // Constructor Int(); Int(int8_t value) : Int(static_cast(value)) {} Int(uint8_t value) : Int(static_cast(value)) {} Int(short value) : Int(static_cast(value)) {} Int(unsigned short value) : Int(static_cast(value)) {} Int(int value); Int(uint64_t value); Int(int64_t value); // Constructor from string explicit Int(std::string_view str, int base = 10); Int(const Int& other); Int(Int&& other) noexcept; // Destructor ~Int() = default; // Assignment operator Int& operator=(const Int& other); Int& operator=(Int&& other) noexcept; Int& operator=(int value); // Generate special values [[nodiscard]] static Int Zero() { return Int(0); } [[nodiscard]] static Int One() { return Int(1); } [[nodiscard]] static Int NaN(); [[nodiscard]] static Int PositiveInfinity(); [[nodiscard]] static Int NegativeInfinity(); [[nodiscard]] static Int fromState(NumericState state, NumericError error = NumericError::None) { Int result; // Initialize appropriately based on the state switch (state) { case NumericState::Normal: // Ordinary value (initialized to zero) result = Int(0); break; case NumericState::NaN: // NaN state result.m_state = NumericState::NaN; result.m_error = error; result.m_sign = 0; // For NaN, the sign is meaningless result.m_words.clear(); // Clear the word array break; case NumericState::PositiveInfinity: // Positive infinity result.m_state = NumericState::PositiveInfinity; result.m_error = error; result.m_sign = 1; // Positive sign result.m_words.clear(); // Clear the word array break; case NumericState::NegativeInfinity: // Negative infinity result.m_state = NumericState::NegativeInfinity; result.m_error = error; result.m_sign = -1; // Negative sign result.m_words.clear(); // Clear the word array break; case NumericState::ComplexInfinity: // Complex infinity result.m_state = NumericState::ComplexInfinity; result.m_error = error; result.m_sign = 0; // The sign is meaningless result.m_words.clear(); // Clear the word array break; case NumericState::Overflow: // Overflow result.m_state = NumericState::Overflow; result.m_error = error; result.m_sign = 1; // Overflow is normally in the positive direction result.m_words.clear(); // Clear the word array break; case NumericState::Underflow: // Underflow (rare for integers, but supported for state completeness) result.m_state = NumericState::Underflow; result.m_error = error; result.m_sign = 1; // Underflow is normally in the positive direction result.m_words.clear(); // Clear the word array break; case NumericState::PositiveZero: // Positive zero (not usually distinguished for multi-precision integers, but supported for state completeness) result.m_state = NumericState::PositiveZero; result.m_error = error; result.m_sign = 0; // The sign of zero is 0 result.m_words.clear(); // Clear the word array break; case NumericState::NegativeZero: // Negative zero (not usually distinguished for multi-precision integers, but supported for state completeness) result.m_state = NumericState::NegativeZero; result.m_error = error; result.m_sign = 0; // The sign of zero is 0 result.m_words.clear(); // Clear the word array break; case NumericState::Divergent: case NumericState::Oscillating: case NumericState::SlowConvergence: case NumericState::NotConverged: case NumericState::TruncatedConvergence: // Convergence-related state result.m_state = state; result.m_error = error; result.m_sign = 0; // The sign is meaningless result.m_words.clear(); // Clear the word array break; default: // Other special states result.m_state = state; result.m_error = error; result.m_sign = 0; // Unsigned by default result.m_words.clear(); // Clear the word array break; } return result; } [[nodiscard]] static Int fromRawWords(const std::vector& words, int sign) { // Create a new Int object Int result; // Return 0 for an empty array if (words.empty()) { result.m_words.clear(); result.m_sign = 0; // The sign of 0 is 0 result.m_state = NumericState::Normal; result.m_error = NumericError::None; return result; } // Copy the word array result.m_words.assign(words.begin(), words.end()); // Set the sign (validating input) if (sign > 0) { result.m_sign = 1; // Positive value } else if (sign < 0) { result.m_sign = -1; // Negative value } else { result.m_sign = 0; // 0 (interpreted as 0 when sign is 0, even if word array is non-empty) result.m_words.clear(); // Clear the word array since the value is 0 } // Set the state to normal result.m_state = NumericState::Normal; result.m_error = NumericError::None; // Strip leading zeros and normalize result.normalize(); return result; } // span-based fromRawWords (zero-copy optimization) [[nodiscard]] static Int fromRawWords(std::span words, int sign) { // Create a new Int object Int result; // Return 0 for an empty array if (words.empty()) { result.m_words.clear(); result.m_sign = 0; result.m_state = NumericState::Normal; result.m_error = NumericError::None; return result; } // Copy from span to vector (ultimately required) result.m_words.assign(words.begin(), words.end()); // Set the sign if (sign > 0) { result.m_sign = 1; } else if (sign < 0) { result.m_sign = -1; } else { result.m_sign = 0; result.m_words.clear(); } // Set the state to normal result.m_state = NumericState::Normal; result.m_error = NumericError::None; // Normalize result.normalize(); return result; } // Fast path for already MSB-normalized data (skips normalize()) // Precondition: the MSB word of words is non-zero (no leading zeros) [[nodiscard]] static Int fromRawWordsPreNormalized(std::span words, int sign) { Int result; if (words.empty()) { result.m_words.clear(); result.m_sign = 0; result.m_state = NumericState::Normal; result.m_error = NumericError::None; return result; } result.m_words.assign(words.begin(), words.end()); result.m_sign = (sign > 0) ? 1 : (sign < 0) ? -1 : 0; if (result.m_sign == 0) result.m_words.clear(); result.m_state = NumericState::Normal; result.m_error = NumericError::None; return result; } // Factory methods for special values (camelCase version) [[nodiscard]] static Int nan(); [[nodiscard]] static Int infinity(); [[nodiscard]] static Int negInfinity(); // State query methods [[nodiscard]] constexpr bool isZero() const { return m_state == NumericState::Normal && m_words.empty(); } [[nodiscard]] constexpr bool isOne() const { // false for special states if (m_state != NumericState::Normal) { return false; } // false unless the sign is positive if (m_sign <= 0) { return false; } // false unless the word count is 1 if (m_words.size() != 1) { return false; } // true if the single word's value is 1 return m_words[0] == 1; } [[nodiscard]] constexpr bool isPositive() const { return m_sign > 0 && !isZero(); } [[nodiscard]] constexpr bool isNegative() const { return m_sign < 0; } [[nodiscard]] constexpr bool isNaN() const { return m_state == NumericState::NaN; } [[nodiscard]] constexpr bool isInfinite() const { return m_state == NumericState::PositiveInfinity || m_state == NumericState::NegativeInfinity; } [[nodiscard]] constexpr bool isSpecialState() const { return m_state != NumericState::Normal; } [[nodiscard]] constexpr int getSign() const { return m_sign; } [[nodiscard]] constexpr NumericState getState() const { return m_state; } [[nodiscard]] constexpr NumericError getError() const { return m_error; } [[nodiscard]] constexpr bool isNormal() const { return m_state == NumericState::Normal; } [[nodiscard]] constexpr bool isDivergent() const { return NumericStateTraits::isDivergent(m_state); } [[nodiscard]] constexpr DivergenceDetail getDivergenceDetail() const { return DivergenceDetail::None; } // Even/odd predicate [[nodiscard]] bool isEven() const { // false for special states if (isSpecialState()) return false; // Zero is even if (isZero()) return true; // Even if the least significant bit is 0 return !getBit(0); } [[nodiscard]] bool isOdd() const { // false for special states if (isSpecialState()) return false; // Zero is even (not odd) if (isZero()) return false; // Odd if the least significant bit is 1 return getBit(0); } // Type-range predicate [[nodiscard]] constexpr bool fitsByte() const { if (isSpecialState()) return false; if (m_words.size() > 1) return false; if (m_words.empty()) return true; uint64_t w = m_words[0]; if (m_sign >= 0) { return w <= 127; } else { return w <= 128; } } [[nodiscard]] constexpr bool fitsUByte() const { if (isSpecialState()) return false; if (m_sign < 0) return false; if (m_words.size() > 1) return false; if (m_words.empty()) return true; return m_words[0] <= 255; } [[nodiscard]] constexpr bool fitsShort() const { if (isSpecialState()) return false; if (m_words.size() > 1) return false; if (m_words.empty()) return true; uint64_t w = m_words[0]; if (m_sign >= 0) { return w <= 32767; } else { return w <= 32768; } } [[nodiscard]] constexpr bool fitsUShort() const { if (isSpecialState()) return false; if (m_sign < 0) return false; if (m_words.size() > 1) return false; if (m_words.empty()) return true; return m_words[0] <= 65535; } [[nodiscard]] constexpr bool fitsInt() const { // Special states do not fit if (isSpecialState()) return false; // int range: [-2^31, 2^31-1] if (m_words.size() > 1) return false; if (m_words.empty()) return true; // Zero uint64_t w = m_words[0]; if (m_sign >= 0) { return w <= static_cast(std::numeric_limits::max()); } else { return w <= (static_cast(std::numeric_limits::max()) + 1); } } [[nodiscard]] constexpr bool fitsUInt() const { if (isSpecialState()) return false; if (m_sign < 0) return false; if (m_words.size() > 1) return false; if (m_words.empty()) return true; return m_words[0] <= static_cast(std::numeric_limits::max()); } [[nodiscard]] constexpr bool fitsInt64() const { // Special states do not fit if (isSpecialState()) return false; // int64_t range: [-2^63, 2^63-1] if (m_words.size() > 1) return false; if (m_words.empty()) return true; // Zero uint64_t w = m_words[0]; if (m_sign >= 0) { return w <= static_cast(std::numeric_limits::max()); } else { return w <= (static_cast(std::numeric_limits::max()) + 1); } } [[nodiscard]] constexpr bool fitsUInt64() const { // Special states do not fit if (isSpecialState()) return false; // Negative numbers do not fit in uint64_t if (m_sign < 0) return false; // uint64_t range: [0, 2^64-1] if (m_words.size() > 1) return false; // Always fits when one word or fewer return true; } // Return the digit count in the specified base [[nodiscard]] size_t digitCount(int base = 10) const; // Fast digit-count estimate in the specified base (GMP mpz_sizeinbase compatible) // Exact for power-of-two bases; otherwise exact or one greater [[nodiscard]] size_t sizeInBase(int base = 10) const; // Integer log2 (= bitLength - 1). Returns 0 for values <= 0 [[nodiscard]] size_t ilog2() const; // Integer log10 (= digitCount(10) - 1). Returns 0 for values <= 0 [[nodiscard]] size_t ilog10() const; // Divisibility test [[nodiscard]] bool isDivisible(const Int& divisor) const; // Congruence test [[nodiscard]] bool isCongruent(const Int& other, const Int& modulus) const; // Swap two Int values void swap(Int& other) noexcept { m_words.swap(other.m_words); std::swap(m_sign, other.m_sign); std::swap(m_state, other.m_state); std::swap(m_error, other.m_error); } // Word access methods [[nodiscard]] constexpr uint64_t word(size_t index) const { return (index < m_words.size()) ? m_words[index] : 0; } [[nodiscard]] constexpr size_t size() const { return m_words.size(); } [[nodiscard]] std::vector words() const { return std::vector(m_words.begin(), m_words.end()); } [[nodiscard]] const uint64_t* data() const noexcept { return m_words.data(); } // Remove leading/trailing zero words in place. Returns the number of low-end words removed. // Used by Float::normalize() for exponent adjustment. size_t trimZeroWords(); // Numeric conversions [[nodiscard]] int toInt() const; [[nodiscard]] int64_t toInt64() const; [[nodiscard]] uint64_t toUInt64() const; [[nodiscard]] size_t toSizeT() const; [[nodiscard]] double toDouble() const; // double * 2^exp decomposition (equivalent to GMP mpz_get_d_2exp) // Return d in [0.5, 1.0); *exp is the exponent of 2. |this| = d * 2^(*exp) // When zero, d=0.0 and *exp=0 [[nodiscard]] double toDouble2Exp(int64_t* exp) const; [[nodiscard]] std::string toString(int base = 10) const; // String-related methods [[nodiscard]] std::string toHexString(bool uppercase = false) const; [[nodiscard]] std::string toBinaryString() const; [[nodiscard]] std::string toOctalString() const; [[nodiscard]] static Int fromString(std::string_view str, int base = 10); // Bit-manipulation methods [[nodiscard]] bool getBit(size_t position) const; void setBit(size_t position); void clearBit(size_t position); void complementBit(size_t position); void combit(size_t position) { complementBit(position); } ///< Alias equivalent to GMP mpz_combit [[nodiscard]] size_t bitLength() const; [[nodiscard]] size_t countLeadingZeros() const; [[nodiscard]] size_t countTrailingZeros() const; // Divisible by 2^b (equivalent to GMP mpz_divisible_2exp_p) // true if the lower b bits are all 0. Always true when b == 0. [[nodiscard]] bool isDivisibleBy2Exp(size_t b) const; // Whether (this - c) is divisible by 2^b (equivalent to GMP mpz_congruent_2exp_p) // true if the lower b bits of this and c match. [[nodiscard]] bool isCongruent2Exp(const Int& c, size_t b) const; // Population count (number of set bits; equivalent to GMP mpz_popcount) // Non-negative integer: return the number of set bits // Negative integer: since two's-complement has infinitely many 1 bits, return SIZE_MAX // (GMP compatible: ULONG_MAX for negatives) [[nodiscard]] size_t popcount() const; // Find the position of the first set/clear bit // GMP: mpz_scan0, mpz_scan1 [[nodiscard]] size_t scanBit0(size_t startPos = 0) const; [[nodiscard]] size_t scanBit1(size_t startPos = 0) const; // Hamming distance (number of differing bit positions) // GMP: mpz_hamdist [[nodiscard]] static size_t hammingDistance(const Int& a, const Int& b); // Unary operators (member functions) [[nodiscard]] Int operator-() const& { if (m_sign == 0) [[likely]] return *this; if (m_state == NumericState::Normal) [[likely]] { Int result = *this; result.m_sign = -m_sign; return result; } return negate_slow(); } [[nodiscard]] Int operator-() && { if (m_sign == 0) [[likely]] return std::move(*this); if (m_state == NumericState::Normal) [[likely]] { m_sign = -m_sign; return std::move(*this); } return negate_slow(); } [[nodiscard]] Int operator+() const { return *this; } // Compound assignment operators (as member functions) Int& operator+=(const Int& other); Int& operator-=(const Int& other); Int& operator*=(const Int& other); Int& operator/=(const Int& other); Int& operator%=(const Int& other); // Single-word compound assignment (equivalent to GMP _ui/_si: avoids building a temporary Int) // The template accepts all integer types and dispatches to uint64_t/int64_t internally template && !std::is_same_v, int> = 0> Int& operator+=(T rhs) { if constexpr (std::is_signed_v) return plusEqScalar(static_cast(rhs)); else return plusEqScalar(static_cast(rhs)); } template && !std::is_same_v, int> = 0> Int& operator-=(T rhs) { if constexpr (std::is_signed_v) return minusEqScalar(static_cast(rhs)); else return minusEqScalar(static_cast(rhs)); } template && !std::is_same_v, int> = 0> Int& operator*=(T rhs) { if constexpr (std::is_signed_v) return mulEqScalar(static_cast(rhs)); else return mulEqScalar(static_cast(rhs)); } template && !std::is_same_v, int> = 0> Int& operator/=(T rhs) { if constexpr (std::is_signed_v) return divEqScalar(static_cast(rhs)); else return divEqScalar(static_cast(rhs)); } template && !std::is_same_v, int> = 0> Int& operator%=(T rhs) { if constexpr (std::is_signed_v) return modEqScalar(static_cast(rhs)); else return modEqScalar(static_cast(rhs)); } private: [[nodiscard]] Int negate_slow() const; // NaN/Infinity path for operator-() Int& plusEqScalar(int64_t rhs); Int& plusEqScalar(uint64_t rhs); Int& minusEqScalar(int64_t rhs); Int& minusEqScalar(uint64_t rhs); Int& mulEqScalar(int64_t rhs); Int& mulEqScalar(uint64_t rhs); Int& divEqScalar(int64_t rhs); Int& divEqScalar(uint64_t rhs); Int& modEqScalar(int64_t rhs); Int& modEqScalar(uint64_t rhs); public: // Bitwise operators (as member functions) Int& operator&=(const Int& other); Int& operator|=(const Int& other); Int& operator<<=(int shift); Int& operator>>=(int shift); // Power assignment operator (a ^= b is equivalent to a = pow(a, b)) Int& operator^=(const Int& other); // State-management methods /// Negate the sign in place (O(1), no copy). /// Since `-a` (operator-) involves a copy, prefer /// `a.negate()` when assigning the result back. void negate() { if (m_state == NumericState::Normal) [[likely]] { m_sign = -m_sign; } } void setSign(int sign) { m_sign = sign; // When in the Normal state with zero value (m_words empty), the sign must be 0 if (sign != 0 && m_state == NumericState::Normal && m_words.empty()) [[unlikely]] { m_sign = 0; } } void setState(NumericState state, NumericError error = NumericError::None); // Friend declarations for comparison operators (C++20 three-way) friend std::partial_ordering operator<=>(const Int& lhs, const Int& rhs); friend bool operator==(const Int& lhs, const Int& rhs); // Friend declarations for bitwise operators friend Int operator~(const Int& value); friend Int operator~(Int&& value); // Friend declarations for stream operators friend std::ostream& operator<<(std::ostream& os, const Int& value); friend std::istream& operator>>(std::istream& is, Int& value); // Friend declarations for utility functions friend class IntOps; friend class IntSpecialStates; friend class IntIOUtils; friend class Float; friend class FloatOps; friend class IntDivision; friend class IntMultiplication; friend class IntRandom; friend class IntSqrt; friend Int abs(const Int& value); friend Int abs(Int&& value); private: // Internal state SboWords m_words; // Array of 64-bit words representing the value (SBO: 4 words inline) int m_sign; // Sign NumericState m_state; // Numeric state NumericError m_error; // Error information // Helper function for absolute-value comparison static int compareAbsoluteValues(const Int& lhs, const Int& rhs); // Method that normalizes the internal state void normalize(); // Set a special state void setNaN(NumericError error = NumericError::None); }; //------------------------------------------------------------------------------ // Global operator declarations //------------------------------------------------------------------------------ // Arithmetic operators [[nodiscard]] Int operator+(const Int& lhs, const Int& rhs); [[nodiscard]] Int operator+(Int&& lhs, const Int& rhs); [[nodiscard]] Int operator+(const Int& lhs, Int&& rhs); [[nodiscard]] Int operator+(Int&& lhs, Int&& rhs); [[nodiscard]] Int operator-(const Int& lhs, const Int& rhs); [[nodiscard]] Int operator-(Int&& lhs, const Int& rhs); [[nodiscard]] Int operator-(const Int& lhs, Int&& rhs); [[nodiscard]] Int operator-(Int&& lhs, Int&& rhs); [[nodiscard]] Int operator*(const Int& lhs, const Int& rhs); [[nodiscard]] Int operator/(const Int& lhs, const Int& rhs); [[nodiscard]] Int operator/(Int&& lhs, const Int& rhs); [[nodiscard]] Int operator%(const Int& lhs, const Int& rhs); [[nodiscard]] Int operator%(Int&& lhs, const Int& rhs); // Single-word arithmetic operators — implementation functions (IntOperators.cpp) // User code should use the template version [[nodiscard]] Int addScalar(const Int& lhs, uint64_t rhs); [[nodiscard]] Int addScalar(Int&& lhs, uint64_t rhs); [[nodiscard]] Int addScalar(const Int& lhs, int64_t rhs); [[nodiscard]] Int addScalar(Int&& lhs, int64_t rhs); [[nodiscard]] Int subScalar(const Int& lhs, uint64_t rhs); [[nodiscard]] Int subScalar(Int&& lhs, uint64_t rhs); [[nodiscard]] Int subScalar(const Int& lhs, int64_t rhs); [[nodiscard]] Int subScalar(Int&& lhs, int64_t rhs); [[nodiscard]] Int rsubScalar(uint64_t lhs, const Int& rhs); [[nodiscard]] Int rsubScalar(int64_t lhs, const Int& rhs); [[nodiscard]] Int mulScalar(const Int& lhs, uint64_t rhs); [[nodiscard]] Int mulScalar(Int&& lhs, uint64_t rhs); [[nodiscard]] Int mulScalar(const Int& lhs, int64_t rhs); [[nodiscard]] Int mulScalar(Int&& lhs, int64_t rhs); [[nodiscard]] Int divScalar(const Int& lhs, uint64_t rhs); [[nodiscard]] Int divScalar(Int&& lhs, uint64_t rhs); [[nodiscard]] Int divScalar(const Int& lhs, int64_t rhs); [[nodiscard]] Int divScalar(Int&& lhs, int64_t rhs); [[nodiscard]] uint64_t modScalarU(const Int& lhs, uint64_t rhs); [[nodiscard]] Int modScalar(const Int& lhs, int64_t rhs); // Single-word comparison operators — implementation functions [[nodiscard]] std::partial_ordering cmpScalar(const Int& lhs, int64_t rhs); [[nodiscard]] bool eqScalar(const Int& lhs, int64_t rhs); [[nodiscard]] std::partial_ordering cmpScalar(const Int& lhs, uint64_t rhs); [[nodiscard]] bool eqScalar(const Int& lhs, uint64_t rhs); // Template operators: accept all integer types and avoid overload ambiguity namespace detail { template struct is_scalar_int : std::bool_constant< std::is_integral_v && !std::is_same_v> {}; template struct is_signed_scalar : std::bool_constant< is_scalar_int::value && std::is_signed_v> {}; template struct is_unsigned_scalar : std::bool_constant< is_scalar_int::value && std::is_unsigned_v> {}; } template::value, int> = 0> [[nodiscard]] Int operator+(const Int& lhs, T rhs) { if constexpr (std::is_signed_v) return addScalar(lhs, static_cast(rhs)); else return addScalar(lhs, static_cast(rhs)); } template::value, int> = 0> [[nodiscard]] Int operator+(Int&& lhs, T rhs) { if constexpr (std::is_signed_v) return addScalar(std::move(lhs), static_cast(rhs)); else return addScalar(std::move(lhs), static_cast(rhs)); } template::value, int> = 0> [[nodiscard]] Int operator+(T lhs, const Int& rhs) { if constexpr (std::is_signed_v) return addScalar(rhs, static_cast(lhs)); else return addScalar(rhs, static_cast(lhs)); } template::value, int> = 0> [[nodiscard]] Int operator-(const Int& lhs, T rhs) { if constexpr (std::is_signed_v) return subScalar(lhs, static_cast(rhs)); else return subScalar(lhs, static_cast(rhs)); } template::value, int> = 0> [[nodiscard]] Int operator-(Int&& lhs, T rhs) { if constexpr (std::is_signed_v) return subScalar(std::move(lhs), static_cast(rhs)); else return subScalar(std::move(lhs), static_cast(rhs)); } template::value, int> = 0> [[nodiscard]] Int operator-(T lhs, const Int& rhs) { if constexpr (std::is_signed_v) return rsubScalar(static_cast(lhs), rhs); else return rsubScalar(static_cast(lhs), rhs); } template::value, int> = 0> [[nodiscard]] Int operator*(const Int& lhs, T rhs) { if constexpr (std::is_signed_v) return mulScalar(lhs, static_cast(rhs)); else return mulScalar(lhs, static_cast(rhs)); } template::value, int> = 0> [[nodiscard]] Int operator*(Int&& lhs, T rhs) { if constexpr (std::is_signed_v) return mulScalar(std::move(lhs), static_cast(rhs)); else return mulScalar(std::move(lhs), static_cast(rhs)); } template::value, int> = 0> [[nodiscard]] Int operator*(T lhs, const Int& rhs) { if constexpr (std::is_signed_v) return mulScalar(rhs, static_cast(lhs)); else return mulScalar(rhs, static_cast(lhs)); } template::value, int> = 0> [[nodiscard]] Int operator/(const Int& lhs, T rhs) { if constexpr (std::is_signed_v) return divScalar(lhs, static_cast(rhs)); else return divScalar(lhs, static_cast(rhs)); } template::value, int> = 0> [[nodiscard]] Int operator/(Int&& lhs, T rhs) { if constexpr (std::is_signed_v) return divScalar(std::move(lhs), static_cast(rhs)); else return divScalar(std::move(lhs), static_cast(rhs)); } template::value, int> = 0> [[nodiscard]] uint64_t operator%(const Int& lhs, T rhs) { return modScalarU(lhs, static_cast(rhs)); } template::value, int> = 0> [[nodiscard]] Int operator%(const Int& lhs, T rhs) { return modScalar(lhs, static_cast(rhs)); } // Single-word comparison operators template::value, int> = 0> [[nodiscard]] std::partial_ordering operator<=>(const Int& lhs, T rhs) { return cmpScalar(lhs, static_cast(rhs)); } template::value, int> = 0> [[nodiscard]] bool operator==(const Int& lhs, T rhs) { return eqScalar(lhs, static_cast(rhs)); } template::value, int> = 0> [[nodiscard]] std::partial_ordering operator<=>(const Int& lhs, T rhs) { return cmpScalar(lhs, static_cast(rhs)); } template::value, int> = 0> [[nodiscard]] bool operator==(const Int& lhs, T rhs) { return eqScalar(lhs, static_cast(rhs)); } // Increment/decrement operators Int& operator++(Int& value); [[nodiscard]] Int operator++(Int& value, int); Int& operator--(Int& value); [[nodiscard]] Int operator--(Int& value, int); // Bitwise operators [[nodiscard]] Int operator&(const Int& lhs, const Int& rhs); [[nodiscard]] Int operator&(Int&& lhs, const Int& rhs); [[nodiscard]] Int operator&(const Int& lhs, Int&& rhs); [[nodiscard]] Int operator&(Int&& lhs, Int&& rhs); [[nodiscard]] Int operator|(const Int& lhs, const Int& rhs); [[nodiscard]] Int operator|(Int&& lhs, const Int& rhs); [[nodiscard]] Int operator|(const Int& lhs, Int&& rhs); [[nodiscard]] Int operator|(Int&& lhs, Int&& rhs); [[nodiscard]] Int operator~(const Int& value); [[nodiscard]] Int operator~(Int&& value); [[nodiscard]] Int operator<<(const Int& lhs, int shift); [[nodiscard]] Int operator<<(Int&& lhs, int shift); [[nodiscard]] Int operator>>(const Int& lhs, int shift); [[nodiscard]] Int operator>>(Int&& lhs, int shift); // Power operator (mathematical notation: a ^ b = pow(a, b)) [[nodiscard]] Int operator^(const Int& lhs, const Int& rhs); // Bitwise XOR (function form) [[nodiscard]] Int bitwiseXor(const Int& lhs, const Int& rhs); [[nodiscard]] Int bitwiseXor(Int&& lhs, const Int& rhs); [[nodiscard]] Int bitwiseXor(const Int& lhs, Int&& rhs); [[nodiscard]] Int bitwiseXor(Int&& lhs, Int&& rhs); // Comparison operators (C++20 three-way) std::partial_ordering operator<=>(const Int& lhs, const Int& rhs); bool operator==(const Int& lhs, const Int& rhs); // Stream operators std::ostream& operator<<(std::ostream& os, const Int& value); std::istream& operator>>(std::istream& is, Int& value); // Utility functions [[nodiscard]] Int abs(const Int& value); [[nodiscard]] Int abs(Int&& value); [[nodiscard]] Int gcd(const Int& a, const Int& b); [[nodiscard]] Int lcm(const Int& a, const Int& b); [[nodiscard]] Int pow(const Int& base, unsigned int exponent); [[nodiscard]] Int pow(const Int& base, const Int& exponent); [[nodiscard]] Int powMod(const Int& base, const Int& exponent, const Int& modulus); [[nodiscard]] std::size_t bitCount(const Int& value); // Factor removal (GMP mpz_remove compatible) // Remove all instances of factor from value, returning {remaining value, count} [[nodiscard]] std::pair removeFactor(const Int& value, const Int& factor); // swap function (defined inside the namespace for ADL) inline void swap(Int& a, Int& b) noexcept { a.swap(b); } [[nodiscard]] bool isProbablePrime(const Int& value, int iterations = 25); } // namespace sangi #endif // SANGI_INT_BASE_HPP