diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 98f39a3..fcd0717 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -37,8 +37,8 @@ jobs: run: sudo apt install doxygen && doxygen --version - name: Install Graphviz run: sudo apt install graphviz - - name: Download awesome style - run: wget https://raw.githubusercontent.com/jothepro/doxygen-awesome-css/main/doxygen-awesome.css && ls +# - name: Download awesome style +# run: wget https://raw.githubusercontent.com/jothepro/doxygen-awesome-css/main/doxygen-awesome.css && ls - name: Create documentation run: doxygen diff --git a/Aesi.h b/Aesi.h index b411b2f..a3c9e33 100755 --- a/Aesi.h +++ b/Aesi.h @@ -32,24 +32,21 @@ class Aesi final { public: /* --------------------- @name Different constructors. ------------------- */ + /** + * @brief Default constructor + */ gpu constexpr Aesi() noexcept = default; + /** + * @brief Copy constructor + * @param copy Aesi& + */ gpu constexpr Aesi(const Aesi& copy) noexcept = default; - template requires (std::is_signed_v) - gpu constexpr Aesi& operator=(Integral value) noexcept { - if(value != 0) { - if(value < 0) { - sign = Sign::Negative; - value *= -1; - } else sign = Sign::Positive; - base = static_cast(value); - } else sign = Sign::Zero; - return *this; - } - - gpu constexpr Aesi& operator=(const Aesi& other) noexcept { base = other.base; sign = other.sign; return *this; } - + /** + * @brief Integral constructor + * @param value Integral + */ template requires (std::is_integral_v) gpu constexpr Aesi(Integral value) noexcept { if(value < 0) { @@ -89,9 +86,17 @@ class Aesi final { } else sign = Sign::Zero; } + /** + * @brief C-style string literal constructor + * @param literal Char[] + */ template requires (arrayLength > 1 && (std::is_same_v || std::is_same_v)) gpu constexpr Aesi(const Char (&literal)[arrayLength]) noexcept : Aesi(literal, arrayLength) {} + /** + * @brief String / String-view constructor + * @param stringView String + */ template requires (std::is_same_v, typename std::decay::type> || std::is_same_v, typename std::decay::type>) gpu constexpr Aesi(String&& stringView) noexcept : Aesi(stringView.data(), stringView.size()) {} @@ -100,15 +105,23 @@ class Aesi final { typename std::decay::type> || std::is_same_v, typename std::decay::type>) gpu constexpr Aesi(const String& stringView) noexcept : Aesi(stringView.data(), stringView.size()) {} + /** + * @brief Unsigned integer conversion + * @param value Aeu& + */ explicit gpu constexpr Aesi(const Aeu& value) : sign(Sign::Positive), base(value) {} #ifdef AESI_CRYPTOPP_INTEGRATION - constexpr Aesi(const CryptoPP::Integer& value) { - if(value.IsZero()) + /** + * @brief Crypto++ library integer constructor + * @param number CryptoPP::Integer& + */ + constexpr Aesi(const CryptoPP::Integer& number) { + if(number.IsZero()) sign = Sign::Zero; else { - base = value; - if(value.IsNegative()) + base = number; + if(number.IsNegative()) sign = Sign::Negative; else sign = Sign::Positive; } @@ -116,27 +129,66 @@ class Aesi final { #endif #ifdef AESI_GMP_INTEGRATION - constexpr Aesi(const mpz_class& value) { - if(value == 0) + /** + * @brief GMP library integer constructor + * @param number mpz_class& + */ + constexpr Aesi(const mpz_class& number) { + if(number == 0) sign = Sign::Zero; else { - base = value; - if(value < 0) + base = number; + if(number < 0) sign = Sign::Negative; else sign = Sign::Positive; } } #endif + + /** + * @brief Integral assignment operator + * @param value Integral + */ + template requires (std::is_signed_v) + gpu constexpr Aesi& operator=(Integral value) noexcept { + if(value != 0) { + if(value < 0) { + sign = Sign::Negative; + value *= -1; + } else sign = Sign::Positive; + base = static_cast(value); + } else sign = Sign::Zero; + return *this; + } + + /** + * @brief Copy assignment operator + * @param other Aesi& + */ + gpu constexpr Aesi& operator=(const Aesi& other) noexcept { base = other.base; sign = other.sign; return *this; } /* ----------------------------------------------------------------------- */ /* --------------------- @name Arithmetic operators. --------------------- */ /* ------------------------- @name Unary operators. -------------------------- */ + /** + * @brief Unary plus operator + * @details Does basically nothing + * @return Aesi + */ gpu constexpr auto operator+() const noexcept -> Aesi { return *this; } + /** + * @brief Unary minus operator + * @return Aesi + */ [[nodiscard]] gpu constexpr auto operator-() const noexcept -> Aesi { Aesi copy = *this; copy.inverse(); return copy; } + /** + * @brief Prefix increment operator + * @return Aesi& + */ gpu constexpr auto operator++() noexcept -> Aesi& { if(sign == Sign::Negative) { --base; if(base.isZero()) sign = Sign::Zero; @@ -144,10 +196,18 @@ class Aesi final { ++base; } else { base = 1u; sign = Sign::Positive; } return *this; - } + } + /** + * @brief Postfix increment operator + * @return Aesi + */ gpu constexpr auto operator++(int) & noexcept -> Aesi { Aesi old = *this; operator++(); return old; } + /** + * @brief Prefix decrement operator + * @return Aesi& + */ gpu constexpr auto operator--() noexcept -> Aesi& { if(sign == Sign::Negative) { ++base; @@ -157,13 +217,27 @@ class Aesi final { return *this; } + /** + * @brief Postfix decrement operator + * @return Aesi + */ gpu constexpr auto operator--(int) & noexcept -> Aesi { Aesi old = *this; operator--(); return old; } /* --------------------------------------------------------------------------- */ /* ------------------------ @name Addition operators. ------------------------ */ + /** + * @brief Adition operator + * @param addendum Aesi& + * @return Aesi + */ [[nodiscard]] gpu constexpr auto operator+(const Aesi& addendum) const noexcept -> Aesi { Aesi result = *this; result += addendum; return result; } + /** + * @brief Addition assignment operator + * @param addendum Aesi& + * @return Aesi& + */ gpu constexpr auto operator+=(const Aesi& addendum) noexcept -> Aesi& { if(addendum.sign == Sign::Zero) /* Any += Zero; */ return *this; @@ -214,9 +288,19 @@ class Aesi final { /* --------------------------------------------------------------------------- */ /* ----------------------- @name Subtraction operators. ---------------------- */ + /** + * @brief Subtraction operator + * @param subtrahend Aesi& + * @return Aesi + */ [[nodiscard]] gpu constexpr auto operator-(const Aesi& subtrahend) const noexcept -> Aesi { Aesi result = *this; result -= subtrahend; return result; } + /** + * @brief Subtraction assignment operator + * @param subtrahend Aesi& + * @return Aesi& + */ gpu constexpr auto operator-=(const Aesi& subtrahend) noexcept -> Aesi& { if(subtrahend.sign == Sign::Zero) /* Any -= Zero; */ return *this; @@ -275,12 +359,27 @@ class Aesi final { /* --------------------------------------------------------------------------- */ /* --------------------- @name Multiplication operators. --------------------- */ + /** + * @brief Multiplication operator for built-in types + * @param factor Integral + * @return Aesi + */ template requires (std::is_integral_v) [[nodiscard]] gpu constexpr auto operator*(Integral factor) const noexcept -> Aesi { Aesi result = *this; result *= factor; return result; } + /** + * @brief Multiplication operator + * @param factor Aesi& + * @return Aesi + */ [[nodiscard]] gpu constexpr auto operator*(const Aesi& factor) const noexcept -> Aesi { Aesi result = *this; result *= factor; return result; } + /** + * @brief Multiplication assignment operator for built-in types + * @param factor Integral + * @return Aesi& + */ template requires (std::is_integral_v) gpu constexpr auto operator*=(Integral factor) noexcept -> Aesi& { if(factor == 0) { @@ -295,6 +394,11 @@ class Aesi final { return *this; } + /** + * @brief Multiplication assignment operator + * @param factor Aesi& + * @return Aesi& + */ gpu constexpr auto operator*=(const Aesi& factor) noexcept -> Aesi& { if(factor.isZero()) { sign = Sign::Zero; @@ -367,12 +471,31 @@ class Aesi final { /* --------------------------------------------------------------------------- */ /* ------------------------- @name Modulo operators. ------------------------- */ + /** + * @brief Modulo operator for built-in types + * @param modulo Integral + * @return Aesi + * @note Returns zero for the modulo of zero + */ template requires (std::is_integral_v) [[nodiscard]] gpu constexpr auto operator%(Integral modulo) const noexcept -> Aesi { Aesi result = *this; result %= modulo; return result; } + /** + * @brief Modulo operator + * @param modulo Aesi& + * @return Aesi + * @details DETAILS + * @note Returns zero for the modulo of zero + */ [[nodiscard]] gpu constexpr auto operator%(const Aesi& modulo) const noexcept -> Aesi { Aesi result = *this; result %= modulo; return result; } + /** + * @brief Modulo assignment operator for built-in types + * @param modulo Integral + * @return Aesi& + * @note Returns zero for the modulo of zero + */ template requires (std::is_integral_v) gpu constexpr auto operator%=(Integral modulo) noexcept -> Aesi& { if(modulo == 0) { @@ -387,6 +510,13 @@ class Aesi final { return *this; } + /** + * @brief Modulo assignment operator + * @param modulo Aesi& + * @return Aesi& + * @details DETAILS + * @note Returns zero for the modulo of zero + */ gpu constexpr auto operator%=(const Aesi& modulo) noexcept -> Aesi& { if(modulo.isZero()) return *this; @@ -404,15 +534,30 @@ class Aesi final { /* --------------------- @name Comparison operators. --------------------- */ /* ------------------------ @name Equality operators. ------------------------ */ + /** + * @brief Equality operator for built-in types + * @param integral Integral + * @return bool + */ template requires (std::is_integral_v) - gpu constexpr auto operator==(Integral value) const noexcept -> bool { - return compareTo(value) == Comparison::equal; + gpu constexpr auto operator==(Integral integral) const noexcept -> bool { + return compareTo(integral) == Comparison::equal; } + /** + * @brief Equality operator + * @param other Aesi& + * @return bool + */ gpu constexpr auto operator==(const Aesi& other) const noexcept -> bool { return sign == other.sign && base == other.base; } + /** + * @brief Different precision equlity operator + * @param other Aesi& + * @return bool + */ template requires (otherBitness != bitness) gpu constexpr auto operator==(const Aesi& other) const noexcept -> bool { return precisionCast() == other; @@ -421,9 +566,14 @@ class Aesi final { /* ----------------------- @name Comparison operators. ----------------------- */ + /** + * @brief Comparison operator for built-in types + * @param integral Integral + * @return Comparison + */ template requires (std::is_integral_v) - gpu constexpr auto compareTo(Integral value) const noexcept -> Comparison { - if(value == 0) { + gpu constexpr auto compareTo(Integral integral) const noexcept -> Comparison { + if(integral == 0) { switch(sign) { case Sign::Positive: return Comparison::greater; @@ -432,10 +582,10 @@ class Aesi final { default: return Comparison::equal; } - } else if(value < 0) { + } else if(integral < 0) { switch(sign) { case Sign::Negative: - switch(base.compareTo(static_cast(value * -1))) { + switch(base.compareTo(static_cast(integral * -1))) { case Comparison::greater: return Comparison::less; case Comparison::less: @@ -450,7 +600,7 @@ class Aesi final { } else { switch(sign) { case Sign::Positive: - return base.compareTo(static_cast(value)); + return base.compareTo(static_cast(integral)); case Sign::Negative: // - < + default: // 0 < + return Comparison::less; @@ -458,11 +608,21 @@ class Aesi final { } } + /** + * @brief Different precision comparison operator + * @param value Aesi& + * @return Comparison + */ template [[nodiscard]] gpu constexpr auto compareTo(const Aesi& value) const noexcept -> Comparison { return precisionCast().compareTo(value); } + /** + * @brief Comparison operator + * @param value Aesi& + * @return Comparison + */ [[nodiscard]] gpu constexpr auto compareTo(const Aesi& value) const noexcept -> Comparison { if(value.isZero()) { @@ -703,6 +863,14 @@ class Aesi final { /* ----------------------------------------------------------------------- */ /* -------------- @name Public arithmetic and number theory. ------------- */ + /** + * @brief Integral division + * @param number Aesi& + * @param divisor Aesi& + * @param quotient Aesi& OUT + * @param remainder Aesi& OUT + * @details Returns values by references + */ gpu static constexpr auto divide(const Aesi& number, const Aesi& divisor, Aesi& quotient, Aesi& remainder) noexcept -> void { if(number.sign == Sign::Zero || divisor.sign == Sign::Zero) { quotient.sign = Sign::Zero; @@ -739,6 +907,11 @@ class Aesi final { remainder.sign = Sign::Zero; } + /** + * @brief Square root + * @return Aesi + * @details Returns zero for negative + */ [[nodiscard]] gpu constexpr auto squareRoot() const noexcept -> Aesi { if(sign == Sign::Positive) @@ -746,6 +919,10 @@ class Aesi final { Aesi result; result.sign = Sign::Zero; return result; } + /** + * @brief Fast exponentiation of 2 + * @return Aesi + */ [[nodiscard]] gpu static constexpr auto power2(std::size_t power) noexcept -> Aesi { Aesi result { Sign::Positive, Aeu::power2(power) }; @@ -755,6 +932,10 @@ class Aesi final { } /* ----------------------------------------------------------------------- */ + /** + * @brief Cast for built-in integral types + * @return Integral + */ template requires (std::is_integral_v) [[nodiscard]] gpu constexpr auto integralCast() const noexcept -> Integral { if(sign == Sign::Zero) @@ -768,6 +949,10 @@ class Aesi final { return base.template integralCast(); } + /** + * @brief Number's precision cast + * @return Aesi + */ template requires (newBitness != bitness) [[nodiscard]] gpu constexpr auto precisionCast() const noexcept -> Aesi { if(sign != Sign::Zero) { @@ -784,9 +969,23 @@ class Aesi final { } else return Aesi {}; } + /** + * @brief Unsigned cast + * @return Aeu + */ gpu constexpr auto unsignedCast() const noexcept -> Aeu { return base; } /* ----------------- @name Public input-output operators. ---------------- */ + /** + * @brief Character buffer output operator + * @param buffer Char* + * @param bufferSize Size_t + * @param showBase Boolean + * @param hexUppercase Boolean + * @return Size_t - amount of symbols written + * @details Places the maximum possible amount of number's characters in buffer. Base parameter should be 2, 8, 10, or 16 + * @note Works significantly faster for hexadecimal notation + */ template requires (std::is_same_v || std::is_same_v && (notation == 2 || notation == 8 || notation == 10 || notation == 16)) gpu constexpr auto getString(Char* buffer, std::size_t bufferSize, bool showBase = false, bool hexUppercase = false) const noexcept -> std::size_t { if(sign != Sign::Zero) { @@ -832,14 +1031,25 @@ class Aesi final { return 1; } + /** + * @brief STD stream output operator + * @param os Ostream + * @param number Aeu + * @return Ostream + * @details Writes number in stream. Accepts STD streams based on char or wchar_t. Supports stream manipulators: + * - Number's notation (std::hex, std::dec, std::oct); + * - Number's base (std::showbase); + * - Hexadecimal letters case (std::uppercase, std::lowercase) + * @note Works significantly faster for hexadecimal notation + */ template requires (std::is_same_v || std::is_same_v) - friend constexpr auto operator<<(std::basic_ostream& ss, const Aesi& value) -> std::basic_ostream& { - if(value.sign != Sign::Zero) { - if(value.sign == Sign::Negative) - ss << [] { if constexpr (std::is_same_v) { return '-'; } else { return L'-'; } } (); - return ss << value.base; + friend constexpr auto operator<<(std::basic_ostream& os, const Aesi& number) -> std::basic_ostream& { + if(number.sign != Sign::Zero) { + if(number.sign == Sign::Negative) + os << [] { if constexpr (std::is_same_v) { return '-'; } else { return L'-'; } } (); + return os << number.base; } - return ss << '0'; + return os << '0'; } /* ----------------------------------------------------------------------- */ @@ -847,18 +1057,24 @@ class Aesi final { /** * @brief Atomicity-oriented object assignment operator * @param Aesi assigning - * @note Method itself is not atomic. There may be race conditions between two consecutive atomic calls on number blocks. + * @note Method itself is not fully-atomic. There may be race conditions between two consecutive atomic calls on number blocks. * This method is an interface for assigning encapsulated class members atomically one by one */ - __device__ constexpr auto tryAtomicSet(const Aesi& value) noexcept -> void {} + __device__ constexpr auto tryAtomicSet(const Aesi& value) noexcept -> void { + base.tryAtomicSet(value.base); + sign = value.sign; /* TODO: Make enum substitution using existing CUDA atomics */ + } /** * @brief Atomicity-oriented object exchangement operator * @param Aesi exchangeable - * @note Method itself is not atomic. There may be race conditions between two consecutive atomic calls on number blocks. + * @note Method itself is not fully-atomic. There may be race conditions between two consecutive atomic calls on number blocks. * This method is an interface for exchanging encapsulated class members atomically one by one */ - __device__ constexpr auto tryAtomicExchange(const Aesi& value) noexcept -> void {} + __device__ constexpr auto tryAtomicExchange(const Aesi& value) noexcept -> void { + base.tryAtomicExchange(value.base); + sign = value.sign; /* TODO: Make enum substitution using existing CUDA atomics */ + } #endif }; diff --git a/Aeu.h b/Aeu.h index 2b3aac7..2f42f26 100755 --- a/Aeu.h +++ b/Aeu.h @@ -52,7 +52,7 @@ namespace { /** * @class Aeu * @brief Long precision unsigned integer - * @details May be used to represent only positive integers. For negative use Aesi. Number precision is set in template parameter bitness. + * @details May be used to represent only positive integers. Number precision is set in template parameter bitness. */ template requires (bitness % blockBitLength == 0) class Aeu final { @@ -1429,7 +1429,7 @@ class Aeu final { /** * @brief Atomicity-oriented object assignment operator * @param assigning Aeu - * @note Method itself is not atomic. There may be race conditions between two consecutive atomic calls on number blocks. + * @note Method itself is not fully-atomic. There may be race conditions between two consecutive atomic calls on number blocks. * This method is an interface for assigning encapsulated class members atomically one by one */ __device__ constexpr auto tryAtomicSet(const Aeu& value) noexcept -> void { @@ -1440,7 +1440,7 @@ class Aeu final { /** * @brief Atomicity-oriented object exchangement operator * @param exchangeable Aeu - * @note Method itself is not atomic. There may be race conditions between two consecutive atomic calls on number blocks. + * @note Method itself is not fully-atomic. There may be race conditions between two consecutive atomic calls on number blocks. * This method is an interface for exchanging encapsulated class members atomically one by one */ __device__ constexpr auto tryAtomicExchange(const Aeu& value) noexcept -> void { diff --git a/README.md b/README.md index 2dbf152..3f2cdea 100755 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Aesi Multiprecision The goal of this project is to develop a fast and handy multi-precision library that can be used with GPU parallelization frameworks such as CUDA, OpenCL, and Metal. The library should correspond to modern C++ standards, support constexpr expressions, and move semantics. > [!IMPORTANT] -> Project is currently in the testing and development stage to support the *Cuda* framework. Please be aware that errors and problems may occur.__ OpenCL support is next in line for development. Metal support is scheduled after some time, due to the presence of significant differences in the framework from Cuda and OpenCL. +> Project is currently in the testing and development stage to support the *Cuda* framework. Please be aware that errors and problems may occur. OpenCL support is next in line for development. Metal support is scheduled after some time, due to the presence of significant differences in the framework from Cuda and OpenCL. > ## Functionality