///////////////////////////////////////////////////////////////////
//  Copyright Christopher Kormanyos 1999 - 2022.                 //
//  Distributed under the Boost Software License,                //
//  Version 1.0. (See accompanying file LICENSE_1_0.txt          //
//  or copy at http://www.boost.org/LICENSE_1_0.txt)             //
///////////////////////////////////////////////////////////////////

#ifndef UINTWIDE_T_2018_10_02_H // NOLINT(llvm-header-guard)
  #define UINTWIDE_T_2018_10_02_H

  #include <algorithm>
  #include <array>
  #include <cinttypes>
  #if !defined(WIDE_INTEGER_DISABLE_FLOAT_INTEROP)
  #include <cmath>
  #endif
  #include <cstddef>
  #include <cstdint>
  #include <cstdlib>
  #include <cstring>
  #include <initializer_list>
  #if !defined(WIDE_INTEGER_DISABLE_IOSTREAM)
  #include <iomanip>
  #include <istream>
  #endif
  #include <iterator>
  #include <limits>
  #if !defined(WIDE_INTEGER_DISABLE_IMPLEMENT_UTIL_DYNAMIC_ARRAY)
  #include <memory>
  #endif
  #if !defined(WIDE_INTEGER_DISABLE_IOSTREAM)
  #include <ostream>
  #include <sstream>
  #endif
  #include <type_traits>

  #if (defined(__clang__) && (__clang_major__ <= 9))
  #define WIDE_INTEGER_NUM_LIMITS_CLASS_TYPE struct // NOLINT(cppcoreguidelines-macro-usage)
  #else
  #define WIDE_INTEGER_NUM_LIMITS_CLASS_TYPE class  // NOLINT(cppcoreguidelines-macro-usage)
  #endif

  #if defined(_MSC_VER)
    #if (_MSC_VER >= 1900) && defined(_HAS_CXX20) && (_HAS_CXX20 != 0)
      #define WIDE_INTEGER_CONSTEXPR constexpr               // NOLINT(cppcoreguidelines-macro-usage)
      #define WIDE_INTEGER_CONSTEXPR_IS_COMPILE_TIME_CONST 1 // NOLINT(cppcoreguidelines-macro-usage)
      #define WIDE_INTEGER_NODISCARD [[nodiscard]]           // NOLINT(cppcoreguidelines-macro-usage)
    #else
      #define WIDE_INTEGER_CONSTEXPR
      #define WIDE_INTEGER_CONSTEXPR_IS_COMPILE_TIME_CONST 0 // NOLINT(cppcoreguidelines-macro-usage)
      #define WIDE_INTEGER_NODISCARD
    #endif
  #else
    #if (defined(__cplusplus) && (__cplusplus >= 201402L))
      #if defined(__AVR__) && (!defined(__GNUC__) || (defined(__GNUC__) && (__GNUC__ > 6)))
      #define WIDE_INTEGER_CONSTEXPR constexpr               // NOLINT(cppcoreguidelines-macro-usage)
      #define WIDE_INTEGER_CONSTEXPR_IS_COMPILE_TIME_CONST 1 // NOLINT(cppcoreguidelines-macro-usage)
      #define WIDE_INTEGER_NODISCARD [[nodiscard]]           // NOLINT(cppcoreguidelines-macro-usage)
      #elif (defined(__cpp_lib_constexpr_algorithms) && (__cpp_lib_constexpr_algorithms>=201806))
        #if defined(__clang__)
          #if (__clang_major__ > 9)
          #define WIDE_INTEGER_CONSTEXPR constexpr               // NOLINT(cppcoreguidelines-macro-usage)
          #define WIDE_INTEGER_CONSTEXPR_IS_COMPILE_TIME_CONST 1 // NOLINT(cppcoreguidelines-macro-usage)
          #define WIDE_INTEGER_NODISCARD [[nodiscard]]           // NOLINT(cppcoreguidelines-macro-usage)
          #else
          #define WIDE_INTEGER_CONSTEXPR
          #define WIDE_INTEGER_CONSTEXPR_IS_COMPILE_TIME_CONST 0 // NOLINT(cppcoreguidelines-macro-usage)
          #define WIDE_INTEGER_NODISCARD
          #endif
        #else
        #define WIDE_INTEGER_CONSTEXPR constexpr               // NOLINT(cppcoreguidelines-macro-usage)
        #define WIDE_INTEGER_CONSTEXPR_IS_COMPILE_TIME_CONST 1 // NOLINT(cppcoreguidelines-macro-usage)
        #define WIDE_INTEGER_NODISCARD [[nodiscard]]           // NOLINT(cppcoreguidelines-macro-usage)
        #endif
      #elif (defined(__clang__) && (__clang_major__ >= 10)) && (defined(__cplusplus) && (__cplusplus > 201703L))
        #if defined(__x86_64__)
        #define WIDE_INTEGER_CONSTEXPR constexpr               // NOLINT(cppcoreguidelines-macro-usage)
        #define WIDE_INTEGER_CONSTEXPR_IS_COMPILE_TIME_CONST 1 // NOLINT(cppcoreguidelines-macro-usage)
        #define WIDE_INTEGER_NODISCARD [[nodiscard]]           // NOLINT(cppcoreguidelines-macro-usage)
        #else
        #define WIDE_INTEGER_CONSTEXPR
        #define WIDE_INTEGER_CONSTEXPR_IS_COMPILE_TIME_CONST 0 // NOLINT(cppcoreguidelines-macro-usage)
        #define WIDE_INTEGER_NODISCARD
        #endif
      #else
      #define WIDE_INTEGER_CONSTEXPR
      #define WIDE_INTEGER_CONSTEXPR_IS_COMPILE_TIME_CONST 0 // NOLINT(cppcoreguidelines-macro-usage)
      #define WIDE_INTEGER_NODISCARD
      #endif
    #else
      #define WIDE_INTEGER_CONSTEXPR
      #define WIDE_INTEGER_CONSTEXPR_IS_COMPILE_TIME_CONST 0 // NOLINT(cppcoreguidelines-macro-usage)
      #define WIDE_INTEGER_NODISCARD
    #endif
  #endif

  #if defined(WIDE_INTEGER_NAMESPACE_BEGIN) || defined(WIDE_INTEGER_NAMESPACE_END)
    #error internal pre-processor macro already defined
  #endif

  #if defined(WIDE_INTEGER_NAMESPACE)
    #define WIDE_INTEGER_NAMESPACE_BEGIN namespace WIDE_INTEGER_NAMESPACE {   // NOLINT(cppcoreguidelines-macro-usage)
    #define WIDE_INTEGER_NAMESPACE_END } // namespace WIDE_INTEGER_NAMESPACE  // NOLINT(cppcoreguidelines-macro-usage)
  #else
    #define WIDE_INTEGER_NAMESPACE_BEGIN
    #define WIDE_INTEGER_NAMESPACE_END
  #endif

  #if !defined(WIDE_INTEGER_DISABLE_IMPLEMENT_UTIL_DYNAMIC_ARRAY)

  WIDE_INTEGER_NAMESPACE_BEGIN

  namespace util {

  template<typename ValueType,
           typename AllocatorType = std::allocator<ValueType>,
           typename SizeType      = std::size_t,
           typename DiffType      = std::ptrdiff_t>
  class dynamic_array;

  template<typename ValueType,
           typename AllocatorType,
           typename SizeType,
           typename DiffType>
  class dynamic_array
  {
  public:
    // Type definitions.
    using allocator_type         = typename std::allocator_traits<AllocatorType>::template rebind_alloc<ValueType>;
    using value_type             = typename allocator_type::value_type;
    using reference              =       value_type&;
    using const_reference        = const value_type&;
    using iterator               =       value_type*;
    using const_iterator         = const value_type*;
    using pointer                =       value_type*;
    using const_pointer          = const value_type*;
    using size_type              =       SizeType;
    using difference_type        =       DiffType;
    using reverse_iterator       =       std::reverse_iterator<iterator>;
    using const_reverse_iterator =       std::reverse_iterator<const_iterator>;

    // Constructors.
    constexpr dynamic_array() : elem_count(0U),
                                elems     (nullptr) { }

    explicit WIDE_INTEGER_CONSTEXPR dynamic_array(      size_type count,
                                                        const_reference v = value_type(),
                                                  const allocator_type& a = allocator_type())
      : elem_count(count),
        elems     (nullptr)
    {
      if(elem_count > 0U)
      {
        allocator_type my_a(a);

        elems = std::allocator_traits<allocator_type>::allocate(my_a, elem_count);

        iterator it = begin();

        while(it != end())
        {
          std::allocator_traits<allocator_type>::construct(my_a, it, v);

          ++it;
        }
      }
    }

    WIDE_INTEGER_CONSTEXPR dynamic_array(const dynamic_array& other)
      : elem_count(other.size()),
        elems     (nullptr)
    {
      allocator_type my_a;

      if(elem_count > 0U)
      {
        elems = std::allocator_traits<allocator_type>::allocate(my_a, elem_count);
      }

      std::copy(other.elems, other.elems + elem_count, elems);
    }

    template<typename input_iterator>
    WIDE_INTEGER_CONSTEXPR dynamic_array(input_iterator first,
                                         input_iterator last,
                                         const allocator_type& a = allocator_type())
      : elem_count(static_cast<size_type>(std::distance(first, last))),
        elems     (nullptr)
    {
      allocator_type my_a(a);

      if(elem_count > 0U)
      {
        elems = std::allocator_traits<allocator_type>::allocate(my_a, elem_count);
      }

      std::copy(first, last, elems);
    }

    WIDE_INTEGER_CONSTEXPR dynamic_array(std::initializer_list<value_type> lst,
                                         const allocator_type& a = allocator_type())
      : elem_count(lst.size()),
        elems     (nullptr)
    {
      allocator_type my_a(a);

      if(elem_count > 0U)
      {
        elems = std::allocator_traits<allocator_type>::allocate(my_a, elem_count);
      }

      std::copy(lst.begin(), lst.end(), elems);
    }

    // Move constructor.
    WIDE_INTEGER_CONSTEXPR dynamic_array(dynamic_array&& other) noexcept : elem_count(other.elem_count),
                                                                           elems     (other.elems)
    {
      other.elem_count = 0U;
      other.elems      = nullptr;
    }

    // Destructor.
    WIDE_INTEGER_CONSTEXPR virtual ~dynamic_array()
    {
      if(!empty())
      {
        using local_allocator_traits_type = std::allocator_traits<allocator_type>;

        allocator_type my_a;

        auto p = begin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto)

        while(p != end())
        {
          local_allocator_traits_type::destroy(my_a, p);

          ++p;
        }

        // Destroy the elements and deallocate the range.
        local_allocator_traits_type::deallocate(my_a, elems, elem_count);
      }
    }

    // Assignment operator.
    WIDE_INTEGER_CONSTEXPR auto operator=(const dynamic_array& other) -> dynamic_array&
    {
      if(this != &other)
      {
        std::copy(other.elems,
                  other.elems + (std::min)(elem_count, other.elem_count),
                  elems);
      }

      return *this;
    }

    // Move assignment operator.
    WIDE_INTEGER_CONSTEXPR auto operator=(dynamic_array&& other) noexcept -> dynamic_array&
    {
      std::swap(elem_count, other.elem_count);
      std::swap(elems,      other.elems);

      return *this;
    }

    // Iterator members:
    WIDE_INTEGER_CONSTEXPR auto begin  ()       -> iterator               { return elems; }
    WIDE_INTEGER_CONSTEXPR auto end    ()       -> iterator               { return elems + elem_count; }
    WIDE_INTEGER_CONSTEXPR auto begin  () const -> const_iterator         { return elems; }
    WIDE_INTEGER_CONSTEXPR auto end    () const -> const_iterator         { return elems + elem_count; }
    WIDE_INTEGER_CONSTEXPR auto cbegin () const -> const_iterator         { return elems; }
    WIDE_INTEGER_CONSTEXPR auto cend   () const -> const_iterator         { return elems + elem_count; }
    WIDE_INTEGER_CONSTEXPR auto rbegin ()       -> reverse_iterator       { return reverse_iterator(elems + elem_count); }
    WIDE_INTEGER_CONSTEXPR auto rend   ()       -> reverse_iterator       { return reverse_iterator(elems); }
    WIDE_INTEGER_CONSTEXPR auto rbegin () const -> const_reverse_iterator { return const_reverse_iterator(elems + elem_count); }
    WIDE_INTEGER_CONSTEXPR auto rend   () const -> const_reverse_iterator { return const_reverse_iterator(elems); }
    WIDE_INTEGER_CONSTEXPR auto crbegin() const -> const_reverse_iterator { return const_reverse_iterator(elems + elem_count); }
    WIDE_INTEGER_CONSTEXPR auto crend  () const -> const_reverse_iterator { return const_reverse_iterator(elems); }

    // Raw pointer access.
    WIDE_INTEGER_CONSTEXPR auto data()       -> pointer       { return elems; }
    WIDE_INTEGER_CONSTEXPR auto data() const -> const_pointer { return elems; }

    // Size and capacity.
    constexpr auto size    () const -> size_type { return  elem_count; }
    constexpr auto max_size() const -> size_type { return  elem_count; }
    constexpr auto empty   () const -> bool      { return (elem_count == 0U); }

    // Element access members.
    WIDE_INTEGER_CONSTEXPR auto operator[](const size_type i)       -> reference       { return elems[i]; }
    WIDE_INTEGER_CONSTEXPR auto operator[](const size_type i) const -> const_reference { return elems[i]; }

    WIDE_INTEGER_CONSTEXPR auto front()       -> reference       { return elems[0U]; }
    WIDE_INTEGER_CONSTEXPR auto front() const -> const_reference { return elems[0U]; }

    WIDE_INTEGER_CONSTEXPR auto back()       -> reference       { return ((elem_count > static_cast<size_type>(0U)) ? elems[elem_count - 1U] : elems[0U]); }
    WIDE_INTEGER_CONSTEXPR auto back() const -> const_reference { return ((elem_count > static_cast<size_type>(0U)) ? elems[elem_count - 1U] : elems[0U]); }

    WIDE_INTEGER_CONSTEXPR auto at(const size_type i)       -> reference       { return ((i < elem_count) ? elems[i] : elems[0U]); }
    WIDE_INTEGER_CONSTEXPR auto at(const size_type i) const -> const_reference { return ((i < elem_count) ? elems[i] : elems[0U]); }

    // Element manipulation members.
    WIDE_INTEGER_CONSTEXPR auto fill(const value_type& v) -> void
    {
      std::fill_n(begin(), elem_count, v);
    }

    WIDE_INTEGER_CONSTEXPR auto swap(dynamic_array& other) noexcept -> void
    {
      if(this != &other)
      {
        std::swap(elems,      other.elems);
        std::swap(elem_count, other.elem_count);
      }
    }

    WIDE_INTEGER_CONSTEXPR auto swap(dynamic_array&& other) noexcept -> void
    {
      auto tmp = std::move(*this);

      *this = std::move(other);
      other = std::move(tmp);
    }

  private:
    mutable size_type elem_count; // NOLINT(readability-identifier-naming)
    pointer           elems;      // NOLINT(readability-identifier-naming,altera-id-dependent-backward-branch)
  };

  template<typename ValueType, typename AllocatorType>
  constexpr auto operator==(const dynamic_array<ValueType, AllocatorType>& lhs,
                            const dynamic_array<ValueType, AllocatorType>& rhs) -> bool
  {
    using local_size_type = typename dynamic_array<ValueType, AllocatorType>::size_type;

    return
    (
         (lhs.size() == rhs.size())
      && (
              (lhs.size() == static_cast<local_size_type>(0U))
           || std::equal(lhs.cbegin(), lhs.cend(), rhs.cbegin())
         )
    );
  }

  template<typename ValueType, typename AllocatorType>
  WIDE_INTEGER_CONSTEXPR auto operator<(const dynamic_array<ValueType, AllocatorType>& lhs,
                                        const dynamic_array<ValueType, AllocatorType>& rhs) -> bool
  {
    using size_type = typename dynamic_array<ValueType, AllocatorType>::size_type;

    const bool size_of_left_is_zero = (lhs.size() == static_cast<size_type>(0U));

    bool b_result { };

    if(size_of_left_is_zero)
    {
      const bool size_of_right_is_zero = (rhs.size() == static_cast<size_type>(0U));

      b_result = (!size_of_right_is_zero);
    }
    else
    {
      if(size_of_left_is_zero)
      {
        const bool size_of_right_is_zero = (rhs.size() == static_cast<size_type>(0U));

        b_result = (!size_of_right_is_zero);
      }
      else
      {
        const size_type count = (std::min)(lhs.size(), rhs.size());

        b_result= std::lexicographical_compare(lhs.cbegin(),
                                               lhs.cbegin() + count,
                                               rhs.cbegin(),
                                               rhs.cbegin() + count);
      }
    }

    return b_result;
  }

  template<typename ValueType, typename AllocatorType>
  WIDE_INTEGER_CONSTEXPR auto operator!=(const dynamic_array<ValueType, AllocatorType>& lhs,
                                         const dynamic_array<ValueType, AllocatorType>& rhs) -> bool
  {
    return (!(lhs == rhs));
  }

  template<typename ValueType, typename AllocatorType>
  WIDE_INTEGER_CONSTEXPR auto operator>(const dynamic_array<ValueType, AllocatorType>& lhs,
                                        const dynamic_array<ValueType, AllocatorType>& rhs) -> bool
  {
    return (rhs < lhs);
  }

  template<typename ValueType, typename AllocatorType>
  WIDE_INTEGER_CONSTEXPR auto operator>=(const dynamic_array<ValueType, AllocatorType>& lhs,
                                         const dynamic_array<ValueType, AllocatorType>& rhs) -> bool
  {
    return (!(lhs < rhs));
  }

  template<typename ValueType, typename AllocatorType>
  WIDE_INTEGER_CONSTEXPR auto operator<=(const dynamic_array<ValueType, AllocatorType>& lhs,
                                         const dynamic_array<ValueType, AllocatorType>& rhs) -> bool
  {
    return (!(rhs < lhs));
  }

  template<typename ValueType, typename AllocatorType>
  WIDE_INTEGER_CONSTEXPR auto swap(dynamic_array<ValueType, AllocatorType>& x,
                                   dynamic_array<ValueType, AllocatorType>& y) noexcept -> void
  {
    x.swap(y);
  }

  } // namespace util

  WIDE_INTEGER_NAMESPACE_END

  WIDE_INTEGER_NAMESPACE_BEGIN

  #if(__cplusplus >= 201703L)
  namespace math::wide_integer::detail {
  #else
  namespace math { namespace wide_integer { namespace detail { // NOLINT(modernize-concat-nested-namespaces)
  #endif

  using util::dynamic_array;

  #if(__cplusplus >= 201703L)
  } // namespace math::wide_integer::detail
  #else
  } // namespace detail
  } // namespace wide_integer
  } // namespace math
  #endif

  WIDE_INTEGER_NAMESPACE_END

  #else

  #include <util/utility/util_dynamic_array.h>

  WIDE_INTEGER_NAMESPACE_BEGIN

  #if(__cplusplus >= 201703L)
  namespace math::wide_integer::detail {
  #else
  namespace math { namespace wide_integer { namespace detail { // NOLINT(modernize-concat-nested-namespaces)
  #endif

  using util::dynamic_array;

  #if(__cplusplus >= 201703L)
  } // namespace math::wide_integer::detail
  #else
  } // namespace detail
  } // namespace wide_integer
  } // namespace math
  #endif

  WIDE_INTEGER_NAMESPACE_END

  #endif

  WIDE_INTEGER_NAMESPACE_BEGIN

  #if(__cplusplus >= 201703L)
  namespace math::wide_integer {
  #else
  namespace math { namespace wide_integer { // NOLINT(modernize-concat-nested-namespaces)
  #endif

  namespace detail {

  using size_t    = std::uint32_t;
  using ptrdiff_t = std::int32_t;

  static_assert((  (std::numeric_limits<size_t>::digits        >= std::numeric_limits<std::uint16_t>::digits)
                && (std::numeric_limits<ptrdiff_t>::digits + 1 >= std::numeric_limits<std::uint16_t>::digits)),
                "Error: size type and pointer difference type must be at least 16 bits in width (or wider)");

  template<const size_t Width2> struct verify_power_of_two // NOLINT(altera-struct-pack-align)
  {
    // TBD: Which powers should be checked if size_t is not 32 bits?
    static constexpr bool conditional_value =
         (Width2 == static_cast<size_t>(1ULL <<  0U)) || (Width2 == static_cast<size_t>(1ULL <<  1U)) || (Width2 == static_cast<size_t>(1ULL <<  2U)) || (Width2 == static_cast<size_t>(1ULL <<  3U))
      || (Width2 == static_cast<size_t>(1ULL <<  4U)) || (Width2 == static_cast<size_t>(1ULL <<  5U)) || (Width2 == static_cast<size_t>(1ULL <<  6U)) || (Width2 == static_cast<size_t>(1ULL <<  7U))
      || (Width2 == static_cast<size_t>(1ULL <<  8U)) || (Width2 == static_cast<size_t>(1ULL <<  9U)) || (Width2 == static_cast<size_t>(1ULL << 10U)) || (Width2 == static_cast<size_t>(1ULL << 11U))
      || (Width2 == static_cast<size_t>(1ULL << 12U)) || (Width2 == static_cast<size_t>(1ULL << 13U)) || (Width2 == static_cast<size_t>(1ULL << 14U)) || (Width2 == static_cast<size_t>(1ULL << 15U))
      || (Width2 == static_cast<size_t>(1ULL << 16U)) || (Width2 == static_cast<size_t>(1ULL << 17U)) || (Width2 == static_cast<size_t>(1ULL << 18U)) || (Width2 == static_cast<size_t>(1ULL << 19U))
      || (Width2 == static_cast<size_t>(1ULL << 20U)) || (Width2 == static_cast<size_t>(1ULL << 21U)) || (Width2 == static_cast<size_t>(1ULL << 22U)) || (Width2 == static_cast<size_t>(1ULL << 23U))
      || (Width2 == static_cast<size_t>(1ULL << 24U)) || (Width2 == static_cast<size_t>(1ULL << 25U)) || (Width2 == static_cast<size_t>(1ULL << 26U)) || (Width2 == static_cast<size_t>(1ULL << 27U))
      || (Width2 == static_cast<size_t>(1ULL << 28U)) || (Width2 == static_cast<size_t>(1ULL << 29U)) || (Width2 == static_cast<size_t>(1ULL << 30U)) || (Width2 == static_cast<size_t>(1ULL << 31U))
      ;
  };

  template<const size_t BitCount,
           typename EnableType = void>
  struct uint_type_helper
  {
    #if defined(WIDE_INTEGER_HAS_LIMB_TYPE_UINT64)
    static_assert((   ((BitCount >= 8U) && (BitCount <= 128U)) // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
                   && (verify_power_of_two<BitCount>::conditional_value)),
                  "Error: uint_type_helper is not intended to be used for this BitCount");
    #else
    static_assert((   ((BitCount >= 8U) && (BitCount <= 64U)) // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
                   && (verify_power_of_two<BitCount>::conditional_value)),
                  "Error: uint_type_helper is not intended to be used for this BitCount");
    #endif

    using exact_unsigned_type = std::uintmax_t;
  };

  template<const size_t BitCount> struct uint_type_helper<BitCount, std::enable_if_t<                     (BitCount <=   8U)>> { using exact_unsigned_type = std::uint8_t;      using fast_unsigned_type = std::uint_fast8_t;  using fast_signed_type = std::int_fast8_t;  }; // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
  template<const size_t BitCount> struct uint_type_helper<BitCount, std::enable_if_t<(BitCount >=  9U) && (BitCount <=  16U)>> { using exact_unsigned_type = std::uint16_t;     using fast_unsigned_type = std::uint_fast16_t; using fast_signed_type = std::int_fast16_t; }; // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
  template<const size_t BitCount> struct uint_type_helper<BitCount, std::enable_if_t<(BitCount >= 17U) && (BitCount <=  32U)>> { using exact_unsigned_type = std::uint32_t;     using fast_unsigned_type = std::uint_fast32_t; using fast_signed_type = std::int_fast32_t; }; // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
  template<const size_t BitCount> struct uint_type_helper<BitCount, std::enable_if_t<(BitCount >= 33U) && (BitCount <=  64U)>> { using exact_unsigned_type = std::uint64_t;     using fast_unsigned_type = std::uint_fast64_t; using fast_signed_type = std::int_fast64_t; }; // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
  #if defined(WIDE_INTEGER_HAS_LIMB_TYPE_UINT64)
  template<const size_t BitCount> struct uint_type_helper<BitCount, typename std::enable_if_t<(BitCount >= 65U) && (BitCount <= 128U)>> { using exact_unsigned_type = unsigned __int128; using fast_unsigned_type = unsigned __int128;  using fast_signed_type = signed __int128;   }; // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
  #endif

  using unsigned_fast_type = typename uint_type_helper<static_cast<size_t>(std::numeric_limits<size_t   >::digits + 0)>::fast_unsigned_type;
  using   signed_fast_type = typename uint_type_helper<static_cast<size_t>(std::numeric_limits<ptrdiff_t>::digits + 1)>::fast_signed_type;

  #if !defined(WIDE_INTEGER_DISABLE_FLOAT_INTEROP)
  namespace my_own {

  template<typename FloatingPointType> WIDE_INTEGER_CONSTEXPR auto frexp     (FloatingPointType x, int* expptr) -> std::enable_if_t<(std::is_floating_point<FloatingPointType>::value &&   std::numeric_limits<FloatingPointType>::is_iec559 ), FloatingPointType>;
  template<typename FloatingPointType> WIDE_INTEGER_CONSTEXPR auto frexp     (FloatingPointType x, int* expptr) -> std::enable_if_t<(std::is_floating_point<FloatingPointType>::value && (!std::numeric_limits<FloatingPointType>::is_iec559)), FloatingPointType>;
  template<typename FloatingPointType> WIDE_INTEGER_CONSTEXPR auto (isfinite)(FloatingPointType x)              -> std::enable_if_t<(std::is_floating_point<FloatingPointType>::value &&   std::numeric_limits<FloatingPointType>::is_iec559 ), bool>;
  template<typename FloatingPointType> WIDE_INTEGER_CONSTEXPR auto (isfinite)(FloatingPointType x)              -> std::enable_if_t<(std::is_floating_point<FloatingPointType>::value && (!std::numeric_limits<FloatingPointType>::is_iec559)), bool>;

  } // namespace my_own
  #endif

  } // namespace detail

  using detail::size_t;
  using detail::ptrdiff_t;
  using detail::unsigned_fast_type;
  using detail::signed_fast_type;

  // Forward declaration of the uintwide_t template class.
  template<const size_t Width2,
           typename LimbType = std::uint32_t,
           typename AllocatorType = void,
           const bool IsSigned = false>
  class uintwide_t;

  // Forward declarations of non-member binary add, sub, mul, div, mod of (uintwide_t op uintwide_t).
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator+(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator-(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator*(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator/(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator%(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;

  // Forward declarations of non-member binary logic operations of (uintwide_t op uintwide_t).
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator| (const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator^ (const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator& (const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;

  // Forward declarations of non-member binary add, sub, mul, div, mod of (uintwide_t op IntegralType).
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator+(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator-(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator*(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator/(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;

  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned>
  constexpr auto operator%(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<(   std::is_integral<IntegralType>::value
                                                                                                                                       && std::is_signed<IntegralType>::value),
                                                                                                                                          uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;

  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned>
  WIDE_INTEGER_CONSTEXPR auto operator%(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<(    std::is_integral   <IntegralType>::value
                                                                                                                                                    &&  std::is_unsigned   <IntegralType>::value
                                                                                                                                                    && (std::numeric_limits<IntegralType>::digits <= std::numeric_limits<LimbType>::digits)),
                                                                                                                                                    typename uintwide_t<Width2, LimbType, AllocatorType, IsSigned>::limb_type>;

  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned>
  constexpr auto operator%(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<(    std::is_integral   <IntegralType>::value
                                                                                                                                       &&  std::is_unsigned   <IntegralType>::value
                                                                                                                                       && (std::numeric_limits<IntegralType>::digits > std::numeric_limits<LimbType>::digits)),
                                                                                                                                       uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;

  // Forward declarations of non-member binary add, sub, mul, div, mod of (IntegralType op uintwide_t).
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator+(const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator-(const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator*(const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator/(const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator%(const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;

  #if !defined(WIDE_INTEGER_DISABLE_FLOAT_INTEROP)
  // Forward declarations of non-member binary add, sub, mul, div, mod of (uintwide_t op FloatingPointType).
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator+(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const FloatingPointType& f) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator-(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const FloatingPointType& f) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator*(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const FloatingPointType& f) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator/(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const FloatingPointType& f) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator%(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const FloatingPointType& f) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;

  // Forward declarations of non-member binary add, sub, mul, div, mod of (FloatingPointType op uintwide_t).
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator+(const FloatingPointType& f, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator-(const FloatingPointType& f, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator*(const FloatingPointType& f, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator/(const FloatingPointType& f, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator%(const FloatingPointType& f, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;
  #endif

  // Forward declarations of non-member binary logic operations of (uintwide_t op IntegralType).
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator|(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator^(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator&(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;

  // Forward declarations of non-member binary binary logic operations of (IntegralType op uintwide_t).
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator|(const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator^(const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator&(const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;

  // Forward declarations of non-member shift functions of (uintwide_t shift IntegralType).
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator<<(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType n) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>; // NOLINT(readability-avoid-const-params-in-decls)
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator>>(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType n) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>; // NOLINT(readability-avoid-const-params-in-decls)

  // Forward declarations of non-member comparison functions of (uintwide_t cmp uintwide_t).
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator==(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> bool;
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator!=(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> bool;
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator> (const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> bool;
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator< (const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> bool;
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator>=(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> bool;
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator<=(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> bool;

  // Forward declarations of non-member comparison functions of (uintwide_t cmp IntegralType).
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator==(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, bool>;
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator!=(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, bool>;
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator> (const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, bool>;
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator< (const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, bool>;
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator>=(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, bool>;
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator<=(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, bool>;

  // Forward declarations of non-member comparison functions of (IntegralType cmp uintwide_t).
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator==(const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, bool>;
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator!=(const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, bool>;
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator> (const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, bool>;
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator< (const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, bool>;
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator>=(const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, bool>;
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator<=(const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, bool>;

  #if !defined(WIDE_INTEGER_DISABLE_FLOAT_INTEROP)
  // Non-member comparison functions of (uintwide_t cmp FloatingPointType).
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator==(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const FloatingPointType& f) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, bool>;
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator!=(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const FloatingPointType& f) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, bool>;
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator> (const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const FloatingPointType& f) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, bool>;
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator< (const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const FloatingPointType& f) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, bool>;
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator>=(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const FloatingPointType& f) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, bool>;
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator<=(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const FloatingPointType& f) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, bool>;

  // Non-member comparison functions of (FloatingPointType cmp uintwide_t).
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator==(const FloatingPointType& f, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, bool>;
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator!=(const FloatingPointType& f, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, bool>;
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator> (const FloatingPointType& f, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, bool>;
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator< (const FloatingPointType& f, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, bool>;
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator>=(const FloatingPointType& f, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, bool>;
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator<=(const FloatingPointType& f, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, bool>;
  #endif

  #if !defined(WIDE_INTEGER_DISABLE_IOSTREAM)

  // Forward declarations of I/O streaming functions.
  template<typename char_type,
           typename traits_type,
           const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  auto operator<<(std::basic_ostream<char_type, traits_type>& out,
                                     const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& x) -> std::basic_ostream<char_type, traits_type>&;

  template<typename char_type,
           typename traits_type,
           const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  auto operator>>(std::basic_istream<char_type, traits_type>& in,
                  uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& x) -> std::basic_istream<char_type, traits_type>&;

  #endif

  // Forward declarations of various number-theoretical tools.
  template<const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  WIDE_INTEGER_CONSTEXPR auto swap(uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& x,
                                   uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& y) noexcept -> void;

  template<const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  WIDE_INTEGER_CONSTEXPR auto lsb(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& x) -> unsigned_fast_type;

  template<const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  WIDE_INTEGER_CONSTEXPR auto msb(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& x) -> unsigned_fast_type;

  template<const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  constexpr auto abs(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& x) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;

  template<const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  WIDE_INTEGER_CONSTEXPR auto sqrt(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& m) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;

  template<const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  WIDE_INTEGER_CONSTEXPR auto cbrt(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& m) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;

  template<const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  WIDE_INTEGER_CONSTEXPR auto rootk(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& m, const std::uint_fast8_t k) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned>; // NOLINT(readability-avoid-const-params-in-decls)

  template<typename OtherUnsignedIntegralTypeP,
           const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  WIDE_INTEGER_CONSTEXPR auto pow(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& b, const OtherUnsignedIntegralTypeP& p) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;

  template<typename OtherUnsignedIntegralTypeP,
           typename OtherUnsignedIntegralTypeM,
           const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  WIDE_INTEGER_CONSTEXPR auto powm(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& b,
                                   const OtherUnsignedIntegralTypeP&    p,
                                   const OtherUnsignedIntegralTypeM&    m) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;

  template<const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  WIDE_INTEGER_CONSTEXPR auto gcd(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& a,
                                  const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& b) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;

  template<typename UnsignedShortType>
  WIDE_INTEGER_CONSTEXPR auto gcd(const UnsignedShortType& u, const UnsignedShortType& v) -> std::enable_if_t<(   (std::is_integral<UnsignedShortType>::value)
                                                                                                               && (std::is_unsigned<UnsignedShortType>::value)), UnsignedShortType>;

  template<const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  WIDE_INTEGER_CONSTEXPR auto lcm(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& a,
                                  const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& b) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;

  template<typename UnsignedShortType>
  WIDE_INTEGER_CONSTEXPR auto lcm(const UnsignedShortType& a, const UnsignedShortType& b) -> std::enable_if_t<(   (std::is_integral<UnsignedShortType>::value)
                                                                                                               && (std::is_unsigned<UnsignedShortType>::value)), UnsignedShortType>;

  template<const size_t Width2,
           typename LimbType = std::uint32_t,
           typename AllocatorType = void,
           const bool IsSigned = false>
  class default_random_engine;

  template<const size_t Width2,
           typename LimbType = std::uint32_t,
           typename AllocatorType = void,
           const bool IsSigned = false>
  class uniform_int_distribution;

  template<const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  constexpr auto operator==(const uniform_int_distribution<Width2, LimbType, AllocatorType, IsSigned>& lhs,
                            const uniform_int_distribution<Width2, LimbType, AllocatorType, IsSigned>& rhs) -> bool;

  template<const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  constexpr auto operator!=(const uniform_int_distribution<Width2, LimbType, AllocatorType, IsSigned>& lhs,
                            const uniform_int_distribution<Width2, LimbType, AllocatorType, IsSigned>& rhs) -> bool;

  template<typename DistributionType,
           typename GeneratorType,
           const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  auto miller_rabin(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& n,
                    const unsigned_fast_type                                     number_of_trials, // NOLINT(readability-avoid-const-params-in-decls)
                          DistributionType&                                      distribution,
                          GeneratorType&                                         generator) -> bool;

  #if(__cplusplus >= 201703L)
  } // namespace math::wide_integer
  #else
  } // namespace wide_integer
  } // namespace math
  #endif

  WIDE_INTEGER_NAMESPACE_END

  namespace std
  {
    // Forward declaration of specialization of std::numeric_limits<uintwide_t>.
    #if defined(WIDE_INTEGER_NAMESPACE)
    template<const WIDE_INTEGER_NAMESPACE::math::wide_integer::size_t Width2,
             typename LimbType,
             typename AllocatorType,
             const bool IsSigned>
    WIDE_INTEGER_NUM_LIMITS_CLASS_TYPE numeric_limits<WIDE_INTEGER_NAMESPACE::math::wide_integer::uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;
    #else
    template<const ::math::wide_integer::size_t Width2,
             typename LimbType,
             typename AllocatorType,
             const bool IsSigned>
    WIDE_INTEGER_NUM_LIMITS_CLASS_TYPE numeric_limits<::math::wide_integer::uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>;
    #endif
  } // namespace std

  WIDE_INTEGER_NAMESPACE_BEGIN

  #if(__cplusplus >= 201703L)
  namespace math::wide_integer::detail {
  #else
  namespace math { namespace wide_integer { namespace detail { // NOLINT(modernize-concat-nested-namespaces)
  #endif

  template<typename MyType,
           const size_t MySize,
           typename MyAlloc>
  class fixed_dynamic_array final : public detail::dynamic_array<MyType, MyAlloc, size_t, ptrdiff_t>
  {
  private:
    using base_class_type = detail::dynamic_array<MyType, MyAlloc, size_t, ptrdiff_t>;

  public:
    static constexpr auto static_size() -> typename base_class_type::size_type { return MySize; }

    explicit WIDE_INTEGER_CONSTEXPR fixed_dynamic_array(const typename base_class_type::size_type       s = MySize,
                                                        const typename base_class_type::value_type&     v = typename base_class_type::value_type(),
                                                        const typename base_class_type::allocator_type& a = typename base_class_type::allocator_type())
      : base_class_type(MySize, typename base_class_type::value_type(), a)
    {
      std::fill(base_class_type::begin(),
                base_class_type::begin() + (std::min)(MySize, static_cast<typename base_class_type::size_type>(s)),
                v);
    }

    constexpr fixed_dynamic_array(const fixed_dynamic_array& other_array)
      : base_class_type(static_cast<const base_class_type&>(other_array)) { }

    WIDE_INTEGER_CONSTEXPR fixed_dynamic_array(std::initializer_list<typename base_class_type::value_type> lst)
      : base_class_type(MySize)
    {
      std::copy(lst.begin(),
                lst.begin() + (std::min)(static_cast<typename base_class_type::size_type>(lst.size()), MySize),
                base_class_type::begin());
    }

    constexpr fixed_dynamic_array(fixed_dynamic_array&& other_array) noexcept
      : base_class_type(static_cast<base_class_type&&>(other_array)) { }

    WIDE_INTEGER_CONSTEXPR auto operator=(const fixed_dynamic_array& other_array) -> fixed_dynamic_array& // NOLINT(cert-oop54-cpp)
    {
      base_class_type::operator=(static_cast<const base_class_type&>(other_array));

      return *this;
    }

    WIDE_INTEGER_CONSTEXPR auto operator=(fixed_dynamic_array&& other_array) noexcept -> fixed_dynamic_array&
    {
      base_class_type::operator=(static_cast<base_class_type&&>(other_array));

      return *this;
    }

    WIDE_INTEGER_CONSTEXPR ~fixed_dynamic_array() override = default;
  };

  template<typename MyType,
           const size_t MySize>
  class fixed_static_array final : public std::array<MyType, static_cast<std::size_t>(MySize)>
  {
  private:
    using base_class_type = std::array<MyType, static_cast<std::size_t>(MySize)>;

  public:
    using size_type  = size_t;
    using value_type = typename base_class_type::value_type;

    static constexpr auto static_size() -> size_type { return MySize; }

    constexpr fixed_static_array() = default;

    explicit WIDE_INTEGER_CONSTEXPR fixed_static_array(const size_type   s,
                                                       const value_type& v = value_type())
    {
      if(s < static_size())
      {
        std::fill(base_class_type::begin(),     base_class_type::begin() + s, v);
        std::fill(base_class_type::begin() + s, base_class_type::end(),       value_type());
      }
      else
      {
        base_class_type::fill(v);
      }
    }

    WIDE_INTEGER_CONSTEXPR fixed_static_array(const fixed_static_array&) = default;
    WIDE_INTEGER_CONSTEXPR fixed_static_array(fixed_static_array&&) noexcept = default;

    WIDE_INTEGER_CONSTEXPR fixed_static_array(std::initializer_list<typename base_class_type::value_type> lst)
    {
      const auto size_to_copy =
        (std::min)(static_cast<size_type>(lst.size()),
                   MySize);

      if(size_to_copy < static_cast<size_type>(base_class_type::size()))
      {
        std::copy(lst.begin(),
                  lst.begin() + size_to_copy,
                  base_class_type::begin());

        std::fill(base_class_type::begin() + size_to_copy,
                  base_class_type::end(),
                  static_cast<typename base_class_type::value_type>(0U));
      }
      else
      {
        std::copy(lst.begin(),
                  lst.begin() + size_to_copy,
                  base_class_type::begin());
      }
    }

    WIDE_INTEGER_CONSTEXPR ~fixed_static_array() = default;

    WIDE_INTEGER_CONSTEXPR auto operator=(const fixed_static_array& other_array) -> fixed_static_array& = default;
    WIDE_INTEGER_CONSTEXPR auto operator=(fixed_static_array&& other_array) noexcept -> fixed_static_array& = default;

    WIDE_INTEGER_CONSTEXPR auto operator[](const size_type i)       -> typename base_class_type::reference       { return base_class_type::operator[](static_cast<typename base_class_type::size_type>(i)); }
    WIDE_INTEGER_CONSTEXPR auto operator[](const size_type i) const -> typename base_class_type::const_reference { return base_class_type::operator[](static_cast<typename base_class_type::size_type>(i)); }
  };

  template<const size_t Width2> struct verify_power_of_two_times_granularity_one_sixty_fourth // NOLINT(altera-struct-pack-align)
  {
    // List of numbers used to identify the form 2^n times 1...63.
    static constexpr bool conditional_value =
       (   verify_power_of_two<static_cast<size_t>(Width2 /  1U)>::conditional_value || verify_power_of_two<static_cast<size_t>(Width2 /  3U)>::conditional_value
        || verify_power_of_two<static_cast<size_t>(Width2 /  5U)>::conditional_value || verify_power_of_two<static_cast<size_t>(Width2 /  7U)>::conditional_value
        || verify_power_of_two<static_cast<size_t>(Width2 /  9U)>::conditional_value || verify_power_of_two<static_cast<size_t>(Width2 / 11U)>::conditional_value
        || verify_power_of_two<static_cast<size_t>(Width2 / 13U)>::conditional_value || verify_power_of_two<static_cast<size_t>(Width2 / 15U)>::conditional_value
        || verify_power_of_two<static_cast<size_t>(Width2 / 17U)>::conditional_value || verify_power_of_two<static_cast<size_t>(Width2 / 19U)>::conditional_value
        || verify_power_of_two<static_cast<size_t>(Width2 / 21U)>::conditional_value || verify_power_of_two<static_cast<size_t>(Width2 / 23U)>::conditional_value
        || verify_power_of_two<static_cast<size_t>(Width2 / 25U)>::conditional_value || verify_power_of_two<static_cast<size_t>(Width2 / 27U)>::conditional_value
        || verify_power_of_two<static_cast<size_t>(Width2 / 29U)>::conditional_value || verify_power_of_two<static_cast<size_t>(Width2 / 31U)>::conditional_value
        || verify_power_of_two<static_cast<size_t>(Width2 / 33U)>::conditional_value || verify_power_of_two<static_cast<size_t>(Width2 / 35U)>::conditional_value
        || verify_power_of_two<static_cast<size_t>(Width2 / 37U)>::conditional_value || verify_power_of_two<static_cast<size_t>(Width2 / 39U)>::conditional_value
        || verify_power_of_two<static_cast<size_t>(Width2 / 41U)>::conditional_value || verify_power_of_two<static_cast<size_t>(Width2 / 43U)>::conditional_value
        || verify_power_of_two<static_cast<size_t>(Width2 / 45U)>::conditional_value || verify_power_of_two<static_cast<size_t>(Width2 / 47U)>::conditional_value
        || verify_power_of_two<static_cast<size_t>(Width2 / 49U)>::conditional_value || verify_power_of_two<static_cast<size_t>(Width2 / 51U)>::conditional_value
        || verify_power_of_two<static_cast<size_t>(Width2 / 53U)>::conditional_value || verify_power_of_two<static_cast<size_t>(Width2 / 55U)>::conditional_value
        || verify_power_of_two<static_cast<size_t>(Width2 / 57U)>::conditional_value || verify_power_of_two<static_cast<size_t>(Width2 / 59U)>::conditional_value
        || verify_power_of_two<static_cast<size_t>(Width2 / 61U)>::conditional_value || verify_power_of_two<static_cast<size_t>(Width2 / 63U)>::conditional_value);
  };

  template<typename UnsignedIntegralType>
  inline WIDE_INTEGER_CONSTEXPR auto lsb_helper(const UnsignedIntegralType& u) -> unsigned_fast_type;

  template<typename UnsignedIntegralType>
  inline WIDE_INTEGER_CONSTEXPR auto msb_helper(const UnsignedIntegralType& u) -> unsigned_fast_type;

  template<>
  inline WIDE_INTEGER_CONSTEXPR auto msb_helper<std::uint32_t>(const std::uint32_t& u) -> unsigned_fast_type;

  template<>
  inline WIDE_INTEGER_CONSTEXPR auto msb_helper<std::uint16_t>(const std::uint16_t& u) -> unsigned_fast_type;

  template<>
  inline WIDE_INTEGER_CONSTEXPR auto msb_helper<std::uint8_t>(const std::uint8_t& u) -> unsigned_fast_type;

  // Use a local implementation of string copy.
  inline WIDE_INTEGER_CONSTEXPR auto strcpy_unsafe(char* dst, const char* src) -> char*
  {
    while((*dst++ = *src++) != '\0') { ; } // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)

    return dst;
  }

  // Use a local implementation of string length.
  inline WIDE_INTEGER_CONSTEXPR auto strlen_unsafe(const char* p_str) -> unsigned_fast_type
  {
    const char* p_str_copy { };

    for(p_str_copy = p_str; (*p_str_copy != '\0'); ++p_str_copy) { ; } // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic,altera-id-dependent-backward-branch)

    return static_cast<unsigned_fast_type>(p_str_copy - p_str);
  }

  template<typename UnsignedShortType,
           typename UnsignedLargeType = typename detail::uint_type_helper<static_cast<size_t>(std::numeric_limits<UnsignedShortType>::digits * 2)>::exact_unsigned_type>
  constexpr auto make_lo(const UnsignedLargeType& u) -> UnsignedShortType
  {
    // From an unsigned integral input parameter of type UnsignedLargeType,
    // extract the low part of it. The type of the extracted
    // low part is UnsignedShortType, which has half the width of UnsignedLargeType.

    using local_ushort_type = UnsignedShortType;
    using local_ularge_type = UnsignedLargeType;

    // Compile-time checks.
    #if defined(WIDE_INTEGER_HAS_LIMB_TYPE_UINT64)
    static_assert(((sizeof(local_ushort_type) * 2U) == sizeof(local_ularge_type)),
                   "Error: Please check the characteristics of the template parameters UnsignedShortType and UnsignedLargeType");
    #else
    static_assert((    ( std::numeric_limits<local_ushort_type>::is_integer)
                   &&  ( std::numeric_limits<local_ularge_type>::is_integer)
                   &&  (!std::numeric_limits<local_ushort_type>::is_signed)
                   &&  (!std::numeric_limits<local_ularge_type>::is_signed)
                   &&  ((sizeof(local_ushort_type) * 2U) == sizeof(local_ularge_type))),
                   "Error: Please check the characteristics of the template parameters UnsignedShortType and UnsignedLargeType");
    #endif

    return static_cast<local_ushort_type>(u);
  }

  template<typename UnsignedShortType,
           typename UnsignedLargeType = typename detail::uint_type_helper<static_cast<size_t>(std::numeric_limits<UnsignedShortType>::digits * 2)>::exact_unsigned_type>
  constexpr auto make_hi(const UnsignedLargeType& u) -> UnsignedShortType
  {
    // From an unsigned integral input parameter of type UnsignedLargeType,
    // extract the high part of it. The type of the extracted
    // high part is UnsignedShortType, which has half the width of UnsignedLargeType.

    using local_ushort_type = UnsignedShortType;
    using local_ularge_type = UnsignedLargeType;

    // Compile-time checks.
    #if defined(WIDE_INTEGER_HAS_LIMB_TYPE_UINT64)
    static_assert(((sizeof(local_ushort_type) * 2U) == sizeof(local_ularge_type)),
                   "Error: Please check the characteristics of the template parameters UnsignedShortType and UnsignedLargeType");
    #else
    static_assert((    ( std::numeric_limits<local_ushort_type>::is_integer)
                   &&  ( std::numeric_limits<local_ularge_type>::is_integer)
                   &&  (!std::numeric_limits<local_ushort_type>::is_signed)
                   &&  (!std::numeric_limits<local_ularge_type>::is_signed)
                   &&  ((sizeof(local_ushort_type) * 2U) == sizeof(local_ularge_type))),
                   "Error: Please check the characteristics of the template parameters UnsignedShortType and UnsignedLargeType");
    #endif

    return static_cast<local_ushort_type>(u >> static_cast<local_ushort_type>(std::numeric_limits<local_ushort_type>::digits));
  }

  template<typename UnsignedShortType,
           typename UnsignedLargeType = typename detail::uint_type_helper<static_cast<size_t>(std::numeric_limits<UnsignedShortType>::digits * 2)>::exact_unsigned_type>
  constexpr auto make_large(const UnsignedShortType& lo, const UnsignedShortType& hi) -> UnsignedLargeType
  {
    // Create a composite unsigned integral value having type UnsignedLargeType.
    // Two constituents are used having type UnsignedShortType, whereby the
    // width of UnsignedShortType is half the width of UnsignedLargeType.

    using local_ushort_type = UnsignedShortType;
    using local_ularge_type = UnsignedLargeType;

    // Compile-time checks.
    #if defined(WIDE_INTEGER_HAS_LIMB_TYPE_UINT64)
    static_assert(((sizeof(local_ushort_type) * 2U) == sizeof(local_ularge_type)),
                   "Error: Please check the characteristics of the template parameters UnsignedShortType and UnsignedLargeType");
    #else
    static_assert((    ( std::numeric_limits<local_ushort_type>::is_integer)
                   &&  ( std::numeric_limits<local_ularge_type>::is_integer)
                   &&  (!std::numeric_limits<local_ushort_type>::is_signed)
                   &&  (!std::numeric_limits<local_ularge_type>::is_signed)
                   &&  ((sizeof(local_ushort_type) * 2U) == sizeof(local_ularge_type))),
                   "Error: Please check the characteristics of the template parameters UnsignedShortType and UnsignedLargeType");
    #endif

    return static_cast<local_ularge_type>(static_cast<local_ularge_type>(static_cast<local_ularge_type>(hi) << static_cast<unsigned>(std::numeric_limits<UnsignedShortType>::digits)) | lo);
  }

  template<typename UnsignedIntegralType>
  constexpr auto negate(UnsignedIntegralType u) -> std::enable_if_t<(   std::is_integral<UnsignedIntegralType>::value
                                                                     && std::is_unsigned<UnsignedIntegralType>::value), UnsignedIntegralType>
  {
    using local_unsigned_integral_type = UnsignedIntegralType;

    return static_cast<local_unsigned_integral_type>((static_cast<local_unsigned_integral_type>(~u)) + 1U);
  }

  template<typename SignedIntegralType>
  constexpr auto negate(SignedIntegralType n) -> std::enable_if_t<(   std::is_integral<SignedIntegralType>::value
                                                                   && std::is_signed<SignedIntegralType>::value), SignedIntegralType>
  {
    using local_signed_integral_type = SignedIntegralType;

    using local_unsigned_integral_type =
      typename detail::uint_type_helper<static_cast<size_t>(std::numeric_limits<local_signed_integral_type>::digits + 1)>::exact_unsigned_type;

    return static_cast<local_signed_integral_type>(negate(static_cast<local_unsigned_integral_type>(n)));
  }

  #if !defined(WIDE_INTEGER_DISABLE_FLOAT_INTEROP)
  template<typename FloatingPointType>
  class native_float_parts final
  {
  public:
    // Emphasize: This template class can be used with native floating-point
    // types like float, double and long double. Note: For long double,
    // you need to verify that the mantissa fits in unsigned long long.
    explicit WIDE_INTEGER_CONSTEXPR native_float_parts(const FloatingPointType f)
    {
      using native_float_type = FloatingPointType;

      static_assert(std::numeric_limits<native_float_type>::digits <= std::numeric_limits<unsigned long long>::digits, // NOLINT(google-runtime-int)
                    "Error: The width of the mantissa does not fit in unsigned long long");

      const native_float_type ff = ((f < static_cast<native_float_type>(0)) ? -f : f);

      if(ff < (std::numeric_limits<native_float_type>::min)())
      {
        return;
      }

      using my_own::frexp;

      // Get the fraction and base-2 exponent.
      auto man = static_cast<native_float_type>(frexp(f, &my_exponent_part));

      unsigned n2 = 0U;

      for(auto i = static_cast<std::uint_fast16_t>(0U); i < static_cast<std::uint_fast16_t>(std::numeric_limits<native_float_type>::digits); ++i)
      {
        // Extract the mantissa of the floating-point type in base-2
        // (one bit at a time) and store it in an unsigned long long.
        man *= 2;

        n2   = static_cast<unsigned>(man);
        man -= static_cast<native_float_type>(n2);

        if(n2 != static_cast<unsigned>(0U))
        {
          my_mantissa_part |= 1U;
        }

        if(i < static_cast<unsigned>(std::numeric_limits<native_float_type>::digits - 1))
        {
          my_mantissa_part <<= 1U;
        }
      }

      // Ensure that the value is normalized and adjust the exponent.
      my_mantissa_part |= static_cast<unsigned long long>(1ULL << static_cast<unsigned>(std::numeric_limits<native_float_type>::digits - 1)); // NOLINT(google-runtime-int)
      my_exponent_part -= 1;
    }

    constexpr native_float_parts(const native_float_parts& other) : my_mantissa_part(other.my_mantissa_part),
                                                                    my_exponent_part(other.my_exponent_part) { }

    constexpr native_float_parts(native_float_parts&& other) noexcept : my_mantissa_part(other.my_mantissa_part),
                                                                        my_exponent_part(other.my_exponent_part) { }

    WIDE_INTEGER_CONSTEXPR ~native_float_parts() = default;

    WIDE_INTEGER_CONSTEXPR auto operator=(const native_float_parts& other) noexcept -> native_float_parts& // NOLINT(cert-oop54-cpp)
    {
      if(this != &other)
      {
        my_mantissa_part = other.my_mantissa_part;
        my_exponent_part = other.my_exponent_part;
      }

      return *this;
    }

    WIDE_INTEGER_CONSTEXPR auto operator=(native_float_parts&& other) noexcept -> native_float_parts&
    {
      my_mantissa_part = other.my_mantissa_part;
      my_exponent_part = other.my_exponent_part;

      return *this;
    }

    WIDE_INTEGER_NODISCARD WIDE_INTEGER_CONSTEXPR auto get_mantissa() const -> unsigned long long { return my_mantissa_part; } // NOLINT(google-runtime-int)
    WIDE_INTEGER_NODISCARD WIDE_INTEGER_CONSTEXPR auto get_exponent() const -> int                { return my_exponent_part; }

    WIDE_INTEGER_CONSTEXPR native_float_parts() = delete;

  private:
    unsigned long long my_mantissa_part { }; // NOLINT(readability-identifier-naming,google-runtime-int)
    int                my_exponent_part { }; // NOLINT(readability-identifier-naming)
  };
  #endif

  #if(__cplusplus >= 201703L)
  } // namespace math::wide_integer::detail
  #else
  } // namespace detail
  } // namespace wide_integer
  } // namespace math
  #endif

  #if(__cplusplus >= 201703L)
  namespace math::wide_integer {
  #else
  namespace math { namespace wide_integer { // NOLINT(modernize-concat-nested-namespaces)
  #endif

  template<const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  class uintwide_t
  {
  public:
    template<const size_t OtherWidth2,
             typename OtherLimbType,
             typename OtherAllocatorType,
             const bool OtherIsSigned>
    friend class uintwide_t;

    // Class-local type definitions.
    using limb_type = LimbType;

    using double_limb_type =
      typename detail::uint_type_helper<static_cast<size_t>(std::numeric_limits<limb_type>::digits * 2)>::exact_unsigned_type;

    // Legacy ularge and ushort types. These are no longer used
    // in the class, but provided for legacy compatibility.
    using ushort_type = limb_type;
    using ularge_type = double_limb_type;

    // More compile-time checks.
    #if defined(WIDE_INTEGER_HAS_LIMB_TYPE_UINT64)
    static_assert(((sizeof(limb_type) * 2U) == sizeof(double_limb_type)),
                   "Error: Please check the characteristics of the template parameters UnsignedShortType and UnsignedLargeType");
    #else
    static_assert((    ( std::numeric_limits<limb_type>::is_integer)
                   &&  ( std::numeric_limits<double_limb_type>::is_integer)
                   &&  (!std::numeric_limits<limb_type>::is_signed)
                   &&  (!std::numeric_limits<double_limb_type>::is_signed)
                   &&  ((sizeof(limb_type) * 2U) == sizeof(double_limb_type))),
                   "Error: Please check the characteristics of the template parameters UnsignedShortType and UnsignedLargeType");
    #endif

    // Helper constants for the digit characteristics.
    static constexpr auto my_width2 = Width2;

    // The number of limbs.
    static constexpr auto number_of_limbs =
      static_cast<size_t>
      (
        my_width2 / static_cast<size_t>(std::numeric_limits<limb_type>::digits)
      );

    static constexpr auto number_of_limbs_karatsuba_threshold = static_cast<size_t>(128U + 1U);

    // Verify that the Width2 template parameter (mirrored with my_width2):
    //   * Is equal to 2^n times 1...63.
    //   * And that there are at least 16, 24 or 32 binary digits, or more.
    //   * And that the number of binary digits is an exact multiple of the number of limbs.
    static_assert(    detail::verify_power_of_two_times_granularity_one_sixty_fourth<my_width2>::conditional_value
                  && (my_width2 >= 16U) // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
                  && (my_width2 == (number_of_limbs * static_cast<size_t>(std::numeric_limits<limb_type>::digits))),
                  "Error: Width2 must be 2^n times 1...63 (with n >= 3), while being 16, 24, 32 or larger, and exactly divisible by limb count");

    // The type of the internal data representation.
    using representation_type =
      typename std::conditional
        <std::is_same<AllocatorType, void>::value,
         detail::fixed_static_array <limb_type,
                                     number_of_limbs>,
         detail::fixed_dynamic_array<limb_type,
                                     number_of_limbs,
                                     typename std::allocator_traits<typename std::conditional<std::is_same<AllocatorType, void>::value,
                                                                                              std::allocator<void>,
                                                                                              AllocatorType>::type>::template rebind_alloc<limb_type>>
        >::type;

    // The iterator types of the internal data representation.
    using iterator               = typename representation_type::iterator;
    using const_iterator         = typename representation_type::const_iterator;
    using reverse_iterator       = typename representation_type::reverse_iterator;
    using const_reverse_iterator = typename representation_type::const_reverse_iterator;

    // Define a class-local type that has double the width of *this.
    using double_width_type = uintwide_t<static_cast<size_t>(Width2 * 2U), limb_type, AllocatorType, IsSigned>;

    // Default constructor.
    constexpr uintwide_t() = default;

    // Constructors from built-in unsigned integral types that
    // are less wide than limb_type or exactly as wide as limb_type.
    template<typename UnsignedIntegralType>
    constexpr uintwide_t(const UnsignedIntegralType v, // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
                         std::enable_if_t<(    std::is_integral   <UnsignedIntegralType>::value
                                           &&  std::is_unsigned   <UnsignedIntegralType>::value
                                           && (std::numeric_limits<UnsignedIntegralType>::digits <= std::numeric_limits<limb_type>::digits))>* = nullptr) // NOLINT(hicpp-named-parameter,readability-named-parameter)
      : values(1U, v) { }

    // Constructors from built-in unsigned integral types that
    // are wider than limb_type, and do not have exactly the
    // same width as limb_type.
    template<typename UnsignedIntegralType>
    WIDE_INTEGER_CONSTEXPR uintwide_t(const UnsignedIntegralType v, // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
                                      std::enable_if_t<(    std::is_integral   <UnsignedIntegralType>::value
                                                        &&  std::is_unsigned   <UnsignedIntegralType>::value
                                                        && (std::numeric_limits<UnsignedIntegralType>::digits > std::numeric_limits<limb_type>::digits))>* p_nullparam = nullptr)
    {
      static_cast<void>(p_nullparam == nullptr);

      auto right_shift_amount_v = static_cast<unsigned_fast_type>(0U);
      auto index_u              = static_cast<std::uint_fast8_t>(0U);

      for( ; (   (static_cast<size_t>(index_u) < values.size()) // NOLINT(altera-id-dependent-backward-branch)
              && (right_shift_amount_v < static_cast<unsigned_fast_type>(std::numeric_limits<UnsignedIntegralType>::digits)));
             ++index_u)
      {
        *(values.begin() + static_cast<size_t>(index_u)) = static_cast<limb_type>(v >> static_cast<unsigned>(right_shift_amount_v));

        right_shift_amount_v += static_cast<unsigned_fast_type>(std::numeric_limits<limb_type>::digits);
      }

      std::fill(values.begin() + static_cast<size_t>(index_u), values.end(), static_cast<limb_type>(0U));
    }

    // Constructors from built-in signed integral types.
    template<typename SignedIntegralType>
    WIDE_INTEGER_CONSTEXPR uintwide_t(const SignedIntegralType v, // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
                                      std::enable_if_t<(   std::is_integral<SignedIntegralType>::value
                                                        && std::is_signed  <SignedIntegralType>::value)>* p_nullparam = nullptr)
    {
      static_cast<void>(p_nullparam == nullptr);

      using local_signed_integral_type   = SignedIntegralType;
      using local_unsigned_integral_type =
        typename detail::uint_type_helper<static_cast<size_t>(std::numeric_limits<local_signed_integral_type>::digits + 1)>::exact_unsigned_type;

      const bool v_is_neg = (v < static_cast<local_signed_integral_type>(0));

      const local_unsigned_integral_type u =
        ((!v_is_neg) ? static_cast<local_unsigned_integral_type>(v)
                     : static_cast<local_unsigned_integral_type>(detail::negate(v)));

      operator=(uintwide_t(u));

      if(v_is_neg) { negate(); }
    }

    #if !defined(WIDE_INTEGER_DISABLE_FLOAT_INTEROP)
    template<typename FloatingPointType,
             std::enable_if_t<(std::is_floating_point<FloatingPointType>::value)> const* = nullptr>
    WIDE_INTEGER_CONSTEXPR uintwide_t(const FloatingPointType f) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
    {
      using local_builtin_float_type = FloatingPointType;

      using detail::my_own::isfinite;

      if(!(isfinite)(f))
      {
        operator=(0U);
      }
      else
      {
        const bool f_is_neg = (f < static_cast<local_builtin_float_type>(0.0F));

        const local_builtin_float_type a = ((!f_is_neg) ? f : -f);

        const bool a_is_zero = (a < static_cast<local_builtin_float_type>(1.0F));

        if(!a_is_zero)
        {
          const detail::native_float_parts<local_builtin_float_type> ld_parts(a);

          // Create a decwide_t from the fractional part of the
          // mantissa expressed as an unsigned long long.
          *this = uintwide_t(ld_parts.get_mantissa());

          // Scale the unsigned long long representation to the fractional
          // part of the long double and multiply with the base-2 exponent.
          const int p2 = ld_parts.get_exponent() - (std::numeric_limits<FloatingPointType>::digits - 1);

          if     (p2 <   0) { *this >>= static_cast<unsigned>(-p2); }
          else if(p2 ==  0) { ; }
          else              { *this <<= static_cast<unsigned>( p2); }

          if(f_is_neg)
          {
            negate();
          }
        }
        else
        {
          operator=(0U);
        }
      }
    }
    #endif

    // Copy constructor.
    #if !defined(WIDE_INTEGER_DISABLE_TRIVIAL_COPY_AND_STD_LAYOUT_CHECKS)
    constexpr uintwide_t(const uintwide_t& other) = default;
    #else
    constexpr uintwide_t(const uintwide_t& other) : values(other.values) { }
    #endif

    // Copy-like constructor from the other signed-ness type.
    template<const bool OtherIsSigned,
             std::enable_if_t<(OtherIsSigned != IsSigned)> const* = nullptr>
    constexpr uintwide_t(const uintwide_t<Width2, LimbType, AllocatorType, OtherIsSigned>& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
      : values(other.values) { }

    // Copy-like constructor from the another type having width that is wider
    // (but has the same limb type) and possibly a different signed-ness.
    template<const size_t OtherWidth2,
             const bool OtherIsSigned,
             std::enable_if_t<(Width2 < OtherWidth2)> const* = nullptr>
    explicit WIDE_INTEGER_CONSTEXPR uintwide_t(const uintwide_t<OtherWidth2, LimbType, AllocatorType, OtherIsSigned>& v)
    {
      using other_wide_integer_type = uintwide_t<OtherWidth2, LimbType, AllocatorType, OtherIsSigned>;

      const bool v_is_neg = (other_wide_integer_type::is_neg(v));

      constexpr auto sz = static_cast<size_t>(number_of_limbs);

      if(!v_is_neg)
      {
        std::copy(v.crepresentation().cbegin(),
                  v.crepresentation().cbegin() + sz,
                  values.begin());
      }
      else
      {
        const other_wide_integer_type uv(-v);

        std::copy(uv.crepresentation().cbegin(),
                  uv.crepresentation().cbegin() + sz,
                  values.begin());

        negate();
      }
    }

    // Copy-like constructor from the another type having width that is less wide
    // (but has the same limb type) and possibly a different signed-ness.
    template<const size_t OtherWidth2,
             const bool OtherIsSigned,
             std::enable_if_t<(Width2 > OtherWidth2)> const* = nullptr>
    explicit WIDE_INTEGER_CONSTEXPR uintwide_t(const uintwide_t<OtherWidth2, LimbType, AllocatorType, OtherIsSigned>& v)
    {
      using other_wide_integer_type = uintwide_t<OtherWidth2, LimbType, AllocatorType, OtherIsSigned>;

      const bool v_is_neg = (other_wide_integer_type::is_neg(v));

      constexpr auto sz = static_cast<size_t>(other_wide_integer_type::number_of_limbs);

      if(!v_is_neg)
      {
        std::copy(v.crepresentation().cbegin(),
                  v.crepresentation().cbegin() + sz,
                  values.begin());

        std::fill(values.begin() + sz, values.end(), static_cast<limb_type>(0U));
      }
      else
      {
        const other_wide_integer_type uv(-v);

        std::copy(uv.crepresentation().cbegin(),
                  uv.crepresentation().cbegin() + sz,
                  values.begin());

        std::fill(values.begin() + sz, values.end(), static_cast<limb_type>(0U));

        negate();
      }
    }

    // Constructor from a constant character string.
    WIDE_INTEGER_CONSTEXPR uintwide_t(const char* str_input) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
    {
      if(!rd_string(str_input))
      {
        static_cast<void>(operator=((std::numeric_limits<uintwide_t>::max)()));
      }
    }

    // Move constructor.
    constexpr uintwide_t(uintwide_t&& other) noexcept = default;

    // Move-like constructor from the other signed-ness type.
    // This constructor is non-explicit because it is a trivial conversion.
    template<const bool OtherIsSigned,
             std::enable_if_t<(IsSigned != OtherIsSigned)> const* = nullptr>
    constexpr uintwide_t(uintwide_t<Width2, LimbType, AllocatorType, OtherIsSigned>&& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
      : values(static_cast<representation_type&&>(other.values)) { }

    // Default destructor.
    WIDE_INTEGER_CONSTEXPR ~uintwide_t() = default;

    // Assignment operator.
    WIDE_INTEGER_CONSTEXPR auto operator=(const uintwide_t& other) -> uintwide_t& = default;

    // Assignment operator from the other signed-ness type.
    template<const bool OtherIsSigned,
             std::enable_if_t<(OtherIsSigned != IsSigned)> const* = nullptr>
    WIDE_INTEGER_CONSTEXPR auto operator=(const uintwide_t<Width2, LimbType, AllocatorType, OtherIsSigned>& other) -> uintwide_t&
    {
      values = other.values;

      return *this;
    }

    // Trivial move assignment operator.
    WIDE_INTEGER_CONSTEXPR auto operator=(uintwide_t&& other) noexcept -> uintwide_t& = default;

    // Trivial move assignment operator from the other signed-ness type.
    template<const bool OtherIsSigned,
             std::enable_if_t<(IsSigned != OtherIsSigned)> const* = nullptr>
    WIDE_INTEGER_CONSTEXPR auto operator=(uintwide_t<Width2, LimbType, AllocatorType, OtherIsSigned>&& other) -> uintwide_t&
    {
      values = static_cast<representation_type&&>(other.values);

      return *this;
    }

    #if !defined(WIDE_INTEGER_DISABLE_FLOAT_INTEROP)
    explicit constexpr operator long double() const { return extract_builtin_floating_point_type<long double>(); }
    explicit constexpr operator double     () const { return extract_builtin_floating_point_type<double>     (); }
    explicit constexpr operator float      () const { return extract_builtin_floating_point_type<float>      (); }
    #endif

    template<typename IntegralType,
             typename = std::enable_if_t<std::is_integral<IntegralType>::value>>
    explicit constexpr operator IntegralType() const
    {
      using local_integral_type = IntegralType;

      return ((!is_neg(*this))
               ? extract_builtin_integral_type<local_integral_type>()
               : detail::negate((-*this).template extract_builtin_integral_type<local_integral_type>()));
    }

    // Cast operator to built-in Boolean type.
    explicit constexpr operator bool() const { return (!is_zero()); }

    // Cast operator that casts to a uintwide_t possibly having a different width
    // and/or possibly having a different signed-ness, but having the same limb type.
    template<const size_t OtherWidth2,
             const bool OtherIsSigned>
    WIDE_INTEGER_CONSTEXPR operator uintwide_t<OtherWidth2, LimbType, AllocatorType, OtherIsSigned>() const // NOLINT(hicpp-explicit-conversions,google-explicit-constructor)
    {
      const bool this_is_neg = (is_neg(*this));

      using other_wide_integer_type = uintwide_t<OtherWidth2, LimbType, AllocatorType, OtherIsSigned>;

      constexpr auto sz =
        static_cast<size_t>
        (
          (Width2 < OtherWidth2) ? static_cast<size_t>(number_of_limbs)
                                 : static_cast<size_t>(other_wide_integer_type::number_of_limbs)
        );

      other_wide_integer_type other;

      if(!this_is_neg)
      {
        std::copy(crepresentation().cbegin(),
                  crepresentation().cbegin() + sz,
                  other.values.begin());

        if(Width2 < OtherWidth2)
        {
          std::fill(other.values.begin() + sz, other.values.end(), static_cast<limb_type>(0U));
        }
      }
      else
      {
        other_wide_integer_type uv(*this);

        uv.negate();

        std::copy(uv.crepresentation().cbegin(),
                  uv.crepresentation().cbegin() + sz,
                  other.values.begin());

        if(Width2 < OtherWidth2)
        {
          std::fill(other.values.begin() + sz, other.values.end(), static_cast<limb_type>(0U));
        }

        other.negate();
      }

      return other;
    }

    // Provide a user interface to the internal data representation.
                           WIDE_INTEGER_CONSTEXPR auto  representation()       ->       representation_type& { return values; }
    WIDE_INTEGER_NODISCARD WIDE_INTEGER_CONSTEXPR auto  representation() const -> const representation_type& { return values; }
    WIDE_INTEGER_NODISCARD WIDE_INTEGER_CONSTEXPR auto crepresentation() const -> const representation_type& { return values; }

    // Unary operators: not, plus and minus.
    WIDE_INTEGER_CONSTEXPR auto operator+() const -> const uintwide_t& { return *this; }
    WIDE_INTEGER_CONSTEXPR auto operator-() const ->       uintwide_t  { uintwide_t tmp(*this); tmp.negate(); return tmp; }

    WIDE_INTEGER_CONSTEXPR auto operator+=(const uintwide_t& other) -> uintwide_t&
    {
      if(this == &other)
      {
        const uintwide_t self(other);

        // Unary addition function.
        const limb_type carry = eval_add_n(values.data(),
                                           values.data(),
                                           self.values.data(),
                                           static_cast<unsigned_fast_type>(number_of_limbs),
                                           static_cast<limb_type>(0U));

        static_cast<void>(carry);
      }
      else
      {
        // Unary addition function.
        const limb_type carry = eval_add_n(values.data(),
                                           values.data(),
                                           other.values.data(),
                                           static_cast<unsigned_fast_type>(number_of_limbs),
                                           static_cast<limb_type>(0U));

        static_cast<void>(carry);
      }

      return *this;
    }

    WIDE_INTEGER_CONSTEXPR auto operator-=(const uintwide_t& other) -> uintwide_t&
    {
      if(this == &other)
      {
        values.fill(0U);
      }
      else
      {
        // Unary subtraction function.
        const limb_type has_borrow = eval_subtract_n(values.data(),
                                                     values.data(),
                                                     other.values.data(),
                                                     number_of_limbs,
                                                     false);

        static_cast<void>(has_borrow);
      }

      return *this;
    }

    WIDE_INTEGER_CONSTEXPR auto operator*=(const uintwide_t& other) -> uintwide_t&
    {
      if(this == &other)
      {
        const uintwide_t other_as_self_copy(other); // NOLINT(performance-unnecessary-copy-initialization)

        eval_mul_unary(*this, other_as_self_copy);
      }
      else
      {
        eval_mul_unary(*this, other);
      }

      return *this;
    }

    WIDE_INTEGER_CONSTEXPR auto mul_by_limb(const limb_type v) -> uintwide_t&
    {
      if(v == static_cast<limb_type>(0U))
      {
        values.fill(0U);
      }
      else if(v > static_cast<limb_type>(1U))
      {
        static_cast<void>(eval_multiply_1d(values.data(),
                                           values.data(),
                                           v,
                                           number_of_limbs));
      }

      return *this;
    }

    WIDE_INTEGER_CONSTEXPR auto operator/=(const uintwide_t& other) -> uintwide_t&
    {
      if(this == &other)
      {
        values.front() = 1U;

        std::fill(values.begin() + 1U, values.end(), static_cast<limb_type>(0U));
      }
      else if(other.is_zero())
      {
        *this = limits_helper_max(IsSigned);
      }
      else
      {
        // Unary division function.

        const bool numererator_was_neg = is_neg(*this);
        const bool denominator_was_neg = is_neg(other);

        if(numererator_was_neg || denominator_was_neg)
        {
          using local_unsigned_wide_type = uintwide_t<Width2, limb_type, AllocatorType, false>;

          local_unsigned_wide_type a(*this);
          local_unsigned_wide_type b(other);

          if(numererator_was_neg) { a.negate(); }
          if(denominator_was_neg) { b.negate(); }

          a.eval_divide_knuth(b);

          if(numererator_was_neg != denominator_was_neg) { a.negate(); }

          values = a.values;
        }
        else
        {
          eval_divide_knuth(other);
        }
      }

      return *this;
    }

    WIDE_INTEGER_CONSTEXPR auto operator%=(const uintwide_t& other) -> uintwide_t&
    {
      if(this == &other)
      {
        std::fill(values.begin(), values.end(), static_cast<limb_type>(0U));
      }
      else
      {
        // Unary modulus function.
        const bool numererator_was_neg = is_neg(*this);
        const bool denominator_was_neg = is_neg(other);

        if(numererator_was_neg || denominator_was_neg)
        {
          using local_unsigned_wide_type = uintwide_t<Width2, limb_type, AllocatorType, false>;

          local_unsigned_wide_type a(*this);
          local_unsigned_wide_type b(other);

          if(numererator_was_neg) { a.negate(); }
          if(denominator_was_neg) { b.negate(); }

          local_unsigned_wide_type remainder;

          a.eval_divide_knuth(b, &remainder);

          // The sign of the remainder follows the sign of the denominator.
          if(numererator_was_neg) { remainder.negate(); }

          values = remainder.values;
        }
        else
        {
          uintwide_t remainder;

          eval_divide_knuth(other, &remainder);

          values = remainder.values;
        }
      }

      return *this;
    }

    // Operators pre-increment and pre-decrement.
    WIDE_INTEGER_CONSTEXPR auto operator++()  -> uintwide_t& { preincrement(); return *this; }
    WIDE_INTEGER_CONSTEXPR auto operator--()  -> uintwide_t& { predecrement(); return *this; }

    // Operators post-increment and post-decrement.
    WIDE_INTEGER_CONSTEXPR auto operator++(int) -> uintwide_t { const uintwide_t w(*this); preincrement(); return w; }
    WIDE_INTEGER_CONSTEXPR auto operator--(int) -> uintwide_t { const uintwide_t w(*this); predecrement(); return w; }

    WIDE_INTEGER_CONSTEXPR auto operator~() -> uintwide_t&
    {
      // Perform bitwise NOT.
      bitwise_not();

      return *this;
    }

    WIDE_INTEGER_CONSTEXPR auto operator|=(const uintwide_t& other) -> uintwide_t&
    {
      if(this != &other)
      {
        // Perform bitwise OR.
        for(auto i = static_cast<unsigned_fast_type>(0U); i < number_of_limbs; ++i)
        {
          *(values.begin() + static_cast<size_t>(i)) = static_cast<limb_type>(*(values.cbegin() + static_cast<size_t>(i)) | *(other.values.cbegin() + static_cast<size_t>(i)));
        }
      }

      return *this;
    }

    WIDE_INTEGER_CONSTEXPR auto operator^=(const uintwide_t& other) -> uintwide_t&
    {
      if(this == &other)
      {
        values.fill(0U);
      }
      else
      {
        // Perform bitwise XOR.
        for(auto i = static_cast<unsigned_fast_type>(0U); i < number_of_limbs; ++i)
        {
          *(values.begin() + static_cast<size_t>(i)) = static_cast<limb_type>(*(values.cbegin() + static_cast<size_t>(i)) ^ *(other.values.cbegin() + static_cast<size_t>(i)));
        }
      }

      return *this;
    }

    WIDE_INTEGER_CONSTEXPR auto operator&=(const uintwide_t& other) -> uintwide_t&
    {
      if(this != &other)
      {
        // Perform bitwise AND.
        for(auto i = static_cast<unsigned_fast_type>(0U); i < number_of_limbs; ++i)
        {
          *(values.begin() + static_cast<size_t>(i)) = static_cast<limb_type>(*(values.cbegin() + static_cast<size_t>(i)) & *(other.values.cbegin() + static_cast<size_t>(i)));
        }
      }

      return *this;
    }

    template<typename SignedIntegralType>
    WIDE_INTEGER_CONSTEXPR auto operator<<=(const SignedIntegralType n) -> std::enable_if_t<(   std::is_integral<SignedIntegralType>::value
                                                                                             && std::is_signed  <SignedIntegralType>::value), uintwide_t>&
    {
      // Implement left-shift operator for signed integral argument.
      if(n < static_cast<SignedIntegralType>(0))
      {
        using local_unsigned_type =
          typename detail::uint_type_helper<static_cast<size_t>(std::numeric_limits<SignedIntegralType>::digits + 1)>::exact_unsigned_type;

        operator>>=(static_cast<local_unsigned_type>(detail::negate(n)));
      }
      else if(n > static_cast<SignedIntegralType>(0))
      {
        if(exceeds_width(n))
        {
          std::fill(values.begin(), values.end(), static_cast<limb_type>(0U));
        }
        else
        {
          shl(n);
        }
      }

      return *this;
    }

    template<typename UnsignedIntegralType>
    WIDE_INTEGER_CONSTEXPR auto operator<<=(const UnsignedIntegralType n) -> std::enable_if_t<(     std::is_integral<UnsignedIntegralType>::value
                                                                                               && (!std::is_signed  <UnsignedIntegralType>::value)), uintwide_t>&
    {
      // Implement left-shift operator for unsigned integral argument.
      if(n != static_cast<UnsignedIntegralType>(0))
      {
        if(exceeds_width(n))
        {
          std::fill(values.begin(), values.end(), static_cast<limb_type>(0U));
        }
        else
        {
          shl(n);
        }
      }

      return *this;
    }

    template<typename SignedIntegralType>
    WIDE_INTEGER_CONSTEXPR auto operator>>=(const SignedIntegralType n) -> std::enable_if_t<(   std::is_integral<SignedIntegralType>::value
                                                                                             && std::is_signed  <SignedIntegralType>::value), uintwide_t>&
    {
      // Implement right-shift operator for signed integral argument.
      if(n < static_cast<SignedIntegralType>(0))
      {
        using local_unsigned_type =
          typename detail::uint_type_helper<static_cast<size_t>(std::numeric_limits<SignedIntegralType>::digits + 1)>::exact_unsigned_type;

        operator<<=(static_cast<local_unsigned_type>(detail::negate(n)));
      }
      else if(n > static_cast<SignedIntegralType>(0))
      {
        if(exceeds_width(n))
        {
          // Fill with either 0's or 1's. Note also the implementation-defined
          // behavior of excessive right-shift of negative value.

          std::fill(values.begin(), values.end(), right_shift_fill_value());
        }
        else
        {
          shr(n);
        }
      }

      return *this;
    }

    template<typename UnsignedIntegralType>
    WIDE_INTEGER_CONSTEXPR auto operator>>=(const UnsignedIntegralType n) -> std::enable_if_t<(     std::is_integral<UnsignedIntegralType>::value
                                                                                               && (!std::is_signed  <UnsignedIntegralType>::value)), uintwide_t>&
    {
      // Implement right-shift operator for unsigned integral argument.
      if(n != static_cast<UnsignedIntegralType>(0))
      {
        if(exceeds_width(n))
        {
          std::fill(values.begin(), values.end(), static_cast<limb_type>(0U));
        }
        else
        {
          shr(n);
        }
      }

      return *this;
    }

    // Implement comparison operators.
    constexpr auto operator==(const uintwide_t& other) const -> bool { return (compare(other) == static_cast<std::int_fast8_t>( 0)); }
    constexpr auto operator< (const uintwide_t& other) const -> bool { return (compare(other) == static_cast<std::int_fast8_t>(-1)); }
    constexpr auto operator> (const uintwide_t& other) const -> bool { return (compare(other) == static_cast<std::int_fast8_t>( 1)); }
    constexpr auto operator!=(const uintwide_t& other) const -> bool { return (compare(other) != static_cast<std::int_fast8_t>( 0)); }
    constexpr auto operator<=(const uintwide_t& other) const -> bool { return (compare(other) <= static_cast<std::int_fast8_t>( 0)); }
    constexpr auto operator>=(const uintwide_t& other) const -> bool { return (compare(other) >= static_cast<std::int_fast8_t>( 0)); }

    // Helper functions for supporting std::numeric_limits<>.
    static constexpr auto limits_helper_max(bool is_signed) -> uintwide_t
    {
      return
      (!is_signed)
        ? from_rep
          (
            representation_type
            (
              number_of_limbs, (std::numeric_limits<limb_type>::max)()
            )
          )
        : from_rep
          (
            representation_type
            (
              number_of_limbs, (std::numeric_limits<limb_type>::max)()
            )
          ) ^ (uintwide_t(1U) << (my_width2 - 1))
        ;
    }

    static constexpr auto limits_helper_min(bool is_signed) -> uintwide_t
    {
      return
      (!is_signed)
        ? from_rep
          (
            representation_type
            (
              number_of_limbs, static_cast<limb_type>(0U)
            )
          )
        : from_rep
          (
            representation_type
            (
              number_of_limbs, static_cast<limb_type>(0U)
            )
          ) | (uintwide_t(1U) << (my_width2 - 1))
        ;
    }

    static constexpr auto limits_helper_min() -> uintwide_t
    {
      return uintwide_t(representation_type(number_of_limbs, static_cast<limb_type>(0U)));
    }

    static constexpr auto limits_helper_lowest(bool is_signed) -> uintwide_t
    {
      return
      (!is_signed)
        ? from_rep
          (
            representation_type
            (
              number_of_limbs, static_cast<limb_type>(0U)
            )
          )
        : from_rep
          (
            representation_type
            (
              number_of_limbs, static_cast<limb_type>(0U)
            )
          ) | (uintwide_t(1U) << (my_width2 - 1))
        ;
    }

    // Define the maximum buffer sizes for extracting
    // octal, decimal and hexadecimal string representations.
    static constexpr auto wr_string_max_buffer_size_oct =
      static_cast<size_t>
      (
        (
            8U
          + (((my_width2 % 3U) != 0U) ? 1U : 0U)
          +   (my_width2 / 3U)
        )
      );

    static constexpr auto wr_string_max_buffer_size_hex =
      static_cast<size_t>
      (
        (
            8U
          + (((my_width2 % 4U) != 0U) ? 1U : 0U)
          +   (my_width2 / 4U)
        )
      );

    static constexpr auto wr_string_max_buffer_size_dec =
      static_cast<size_t>
      (
        (
            10U
          + static_cast<size_t>((static_cast<std::uintmax_t>(my_width2) * UINTMAX_C(301)) / UINTMAX_C(1000))
        )
      );

    // Write string function.
    WIDE_INTEGER_CONSTEXPR auto wr_string(      char*              str_result, // NOLINT(readability-function-cognitive-complexity)
                                          const std::uint_fast8_t  base_rep     = 0x10U,
                                          const bool               show_base    = true,
                                          const bool               show_pos     = false,
                                          const bool               is_uppercase = true,
                                                unsigned_fast_type field_width  = 0U,
                                          const char               fill_char    = '0') const -> bool
    {
      bool wr_string_is_ok = true;

      if(base_rep == UINT8_C(8))
      {
        uintwide_t t(*this);

        const auto mask = static_cast<limb_type>(static_cast<std::uint8_t>(0x7U));

        using string_storage_oct_type =
          typename std::conditional
            <my_width2 <= static_cast<size_t>(UINT32_C(2048)),
             detail::fixed_static_array <char,
                                         wr_string_max_buffer_size_oct>,
             detail::fixed_dynamic_array<char,
                                         wr_string_max_buffer_size_oct,
                                         typename std::allocator_traits<typename std::conditional<std::is_same<AllocatorType, void>::value,
                                                                                                  std::allocator<void>,
                                                                                                  AllocatorType>::type>::template rebind_alloc<limb_type>>
            >::type;

        string_storage_oct_type str_temp;

        auto pos = static_cast<unsigned_fast_type>(str_temp.size() - 1U);

        if(t.is_zero())
        {
          --pos;

          str_temp[static_cast<typename string_storage_oct_type::size_type>(pos)] = '0';
        }
        else
        {
          if(!is_neg(t))
          {
            while(!t.is_zero())
            {
              auto c = static_cast<char>(*t.values.cbegin() & mask);

              if(c <= static_cast<char>(INT8_C(8))) { c = static_cast<char>(c + static_cast<char>(INT8_C(0x30))); }

              --pos;

              str_temp[static_cast<typename string_storage_oct_type::size_type>(pos)] = c;

              t >>= 3;
            }
          }
          else
          {
            uintwide_t<my_width2, limb_type, AllocatorType, false> tu(t);

            while(!tu.is_zero()) // NOLINT(altera-id-dependent-backward-branch)
            {
              auto c = static_cast<char>(*tu.values.cbegin() & mask);

              if(c <= static_cast<char>(INT8_C(8))) { c = static_cast<char>(c + static_cast<char>(INT8_C(0x30))); }

              --pos;

              str_temp[static_cast<typename string_storage_oct_type::size_type>(pos)] = c;

              tu >>= 3;
            }
          }
        }

        if(show_base)
        {
          --pos;

          str_temp[static_cast<typename string_storage_oct_type::size_type>(pos)] = '0';
        }

        if(show_pos)
        {
          --pos;

          str_temp[static_cast<typename string_storage_oct_type::size_type>(pos)] = '+';
        }

        if(field_width != 0U)
        {
          field_width = (std::min)(field_width, static_cast<unsigned_fast_type>(str_temp.size() - 1U));

          while(static_cast<signed_fast_type>(pos) > static_cast<signed_fast_type>((str_temp.size() - 1U) - field_width)) // NOLINT(altera-id-dependent-backward-branch)
          {
            --pos;

            str_temp[static_cast<typename string_storage_oct_type::size_type>(pos)] = fill_char;
          }
        }

        str_temp[static_cast<typename string_storage_oct_type::size_type>(str_temp.size() - 1U)] = '\0';

        detail::strcpy_unsafe(str_result, str_temp.data() + pos);
      }
      else if(base_rep == UINT8_C(10))
      {
        uintwide_t t(*this);

        const bool str_has_neg_sign = is_neg(t);

        if(str_has_neg_sign)
        {
          t.negate();
        }

        using string_storage_dec_type =
          typename std::conditional
            <my_width2 <= static_cast<size_t>(UINT32_C(2048)),
             detail::fixed_static_array <char,
                                         wr_string_max_buffer_size_dec>,
             detail::fixed_dynamic_array<char,
                                         wr_string_max_buffer_size_dec,
                                         typename std::allocator_traits<typename std::conditional<std::is_same<AllocatorType, void>::value,
                                                                                                  std::allocator<void>,
                                                                                                  AllocatorType>::type>::template rebind_alloc<limb_type>>
            >::type;

        string_storage_dec_type str_temp;

        auto pos = static_cast<unsigned_fast_type>(str_temp.size() - 1U);

        if(t.is_zero())
        {
          --pos;

          str_temp[static_cast<typename string_storage_dec_type::size_type>(pos)] = '0';
        }
        else
        {
          while(!t.is_zero())
          {
            const uintwide_t tmp(t);

            t.eval_divide_by_single_limb(static_cast<limb_type>(UINT8_C(10)), 0U, nullptr);

            --pos;

            str_temp[static_cast<typename string_storage_dec_type::size_type>(pos)] =
              static_cast<char>
              (
                  static_cast<limb_type>
                  (
                    tmp - (uintwide_t(t).mul_by_limb(static_cast<limb_type>(UINT8_C(10))))
                  )
                + UINT8_C(0x30)
              );
          }
        }

        if(show_pos && (!str_has_neg_sign))
        {
          --pos;

          str_temp[static_cast<typename string_storage_dec_type::size_type>(pos)] = '+';
        }
        else if(str_has_neg_sign)
        {
          --pos;

          str_temp[static_cast<typename string_storage_dec_type::size_type>(pos)] = '-';
        }

        if(field_width != 0U)
        {
          field_width = (std::min)(field_width, static_cast<unsigned_fast_type>(str_temp.size() - 1U));

          while(static_cast<signed_fast_type>(pos) > static_cast<signed_fast_type>((str_temp.size() - 1U) - field_width)) // NOLINT(altera-id-dependent-backward-branch)
          {
            --pos;

            str_temp[static_cast<typename string_storage_dec_type::size_type>(pos)] = fill_char;
          }
        }

        str_temp[static_cast<typename string_storage_dec_type::size_type>(str_temp.size() - 1U)] = '\0';

        detail::strcpy_unsafe(str_result, str_temp.data() + pos);
      }
      else if(base_rep == UINT8_C(16))
      {
        uintwide_t t(*this);

        const auto mask = static_cast<limb_type>(static_cast<std::uint8_t>(0xFU));

        using string_storage_hex_type =
          typename std::conditional
            <my_width2 <= static_cast<size_t>(UINT32_C(2048)),
             detail::fixed_static_array <char,
                                         wr_string_max_buffer_size_hex>,
             detail::fixed_dynamic_array<char,
                                         wr_string_max_buffer_size_hex,
                                         typename std::allocator_traits<typename std::conditional<std::is_same<AllocatorType, void>::value,
                                                                                                  std::allocator<void>,
                                                                                                  AllocatorType>::type>::template rebind_alloc<limb_type>>
            >::type;

        string_storage_hex_type str_temp;

        auto pos = static_cast<unsigned_fast_type>(str_temp.size() - 1U);

        if(t.is_zero())
        {
          --pos;

          str_temp[static_cast<typename string_storage_hex_type::size_type>(pos)] = '0';
        }
        else
        {
          if(!is_neg(t))
          {
            while(!t.is_zero())
            {
              char c(*t.values.cbegin() & mask);

              if      (c <= static_cast<char>(INT8_C(  9)))                                           { c = static_cast<char>(c + static_cast<char>(INT8_C(0x30))); }
              else if((c >= static_cast<char>(INT8_C(0xA))) && (c <= static_cast<char>(INT8_C(0xF)))) { c = static_cast<char>(c + (is_uppercase ? static_cast<char>(INT8_C(55)) : static_cast<char>(INT8_C(87)))); }

              --pos;

              str_temp[static_cast<typename string_storage_hex_type::size_type>(pos)] = c;

              t >>= 4;
            }
          }
          else
          {
            uintwide_t<my_width2, limb_type, AllocatorType, false> tu(t);

            while(!tu.is_zero()) // NOLINT(altera-id-dependent-backward-branch)
            {
              char c(*tu.values.cbegin() & mask);

              if      (c <= static_cast<char>(INT8_C(  9)))                                           { c = static_cast<char>(c + static_cast<char>(INT8_C(0x30))); }
              else if((c >= static_cast<char>(INT8_C(0xA))) && (c <= static_cast<char>(INT8_C(0xF)))) { c = static_cast<char>(c + (is_uppercase ? static_cast<char>(INT8_C(55)) : static_cast<char>(INT8_C(87)))); }

              --pos;

              str_temp[static_cast<typename string_storage_hex_type::size_type>(pos)] = c;

              tu >>= 4;
            }
          }
        }

        if(show_base)
        {
          --pos;

          str_temp[static_cast<typename string_storage_hex_type::size_type>(pos)] = (is_uppercase ? 'X' : 'x');

          --pos;

          str_temp[static_cast<typename string_storage_hex_type::size_type>(pos)] = '0';
        }

        if(show_pos)
        {
          --pos;

          str_temp[static_cast<typename string_storage_hex_type::size_type>(pos)] = '+';
        }

        if(field_width != 0U)
        {
          field_width = (std::min)(field_width, static_cast<unsigned_fast_type>(str_temp.size() - 1U));

          while(static_cast<signed_fast_type>(pos) > static_cast<signed_fast_type>((str_temp.size() - 1U) - field_width)) // NOLINT(altera-id-dependent-backward-branch)
          {
            --pos;

            str_temp[static_cast<typename string_storage_hex_type::size_type>(pos)] = fill_char;
          }
        }

        str_temp[static_cast<typename string_storage_hex_type::size_type>(str_temp.size() - 1U)] = '\0';

        detail::strcpy_unsafe(str_result, str_temp.data() + pos);
      }
      else
      {
        wr_string_is_ok = false;
      }

      return wr_string_is_ok;
    }

    template<const bool RePhraseIsSigned = IsSigned,
             std::enable_if_t<(!RePhraseIsSigned)> const* = nullptr>
    WIDE_INTEGER_NODISCARD WIDE_INTEGER_CONSTEXPR auto compare(const uintwide_t<Width2, LimbType, AllocatorType, RePhraseIsSigned>& other) const -> std::int_fast8_t
    {
      return compare_ranges(values.data(),
                            other.values.data(),
                            uintwide_t<Width2, LimbType, AllocatorType, RePhraseIsSigned>::number_of_limbs);
    }

    template<const bool RePhraseIsSigned = IsSigned,
             std::enable_if_t<RePhraseIsSigned> const* = nullptr>
    WIDE_INTEGER_NODISCARD WIDE_INTEGER_CONSTEXPR auto compare(const uintwide_t<Width2, LimbType, AllocatorType, RePhraseIsSigned>& other) const -> std::int_fast8_t
    {
      auto n_result = std::int_fast8_t { };

      const auto other_is_neg = is_neg(other);
      const auto my_is_neg    = is_neg(*this);

      if(my_is_neg && (!other_is_neg))
      {
        n_result = static_cast<std::int_fast8_t>(INT8_C(-1));
      }
      else if((!my_is_neg) && other_is_neg)
      {
        n_result = static_cast<std::int_fast8_t>(INT8_C(1));
      }
      else
      {
        n_result = compare_ranges(values.data(),
                                  other.values.data(),
                                  uintwide_t<Width2, LimbType, AllocatorType, RePhraseIsSigned>::number_of_limbs);
      }

      return n_result;
    }

    WIDE_INTEGER_CONSTEXPR auto negate() -> void
    {
      bitwise_not();

      preincrement();
    }

    WIDE_INTEGER_CONSTEXPR auto eval_divide_by_single_limb(const limb_type          short_denominator,
                                                           const unsigned_fast_type u_offset,
                                                                 uintwide_t*        remainder) -> void
    {
      // The denominator has one single limb.
      // Use a one-dimensional division algorithm.

      auto long_numerator = static_cast<double_limb_type>(0U);

      auto hi_part = static_cast<limb_type>(0U);

      for(auto i = static_cast<signed_fast_type>(static_cast<unsigned_fast_type>(number_of_limbs - 1U) - u_offset); static_cast<signed_fast_type>(i) >= 0; --i) // NOLINT(altera-id-dependent-backward-branch)
      {
        long_numerator =
          static_cast<double_limb_type>
          (
             static_cast<double_limb_type>(*(values.cbegin() + static_cast<size_t>(i)))
           + static_cast<double_limb_type>(static_cast<double_limb_type>(long_numerator - static_cast<double_limb_type>(static_cast<double_limb_type>(short_denominator) * hi_part)) << static_cast<unsigned>(std::numeric_limits<limb_type>::digits))
          );

        *(values.begin() + static_cast<size_t>(i)) =
          detail::make_lo<limb_type>(static_cast<double_limb_type>(long_numerator / short_denominator));

        hi_part = *(values.cbegin() + static_cast<size_t>(i));
      }

      if(remainder != nullptr)
      {
        long_numerator =
          static_cast<double_limb_type>
          (
             static_cast<double_limb_type>(*values.cbegin())
           + static_cast<double_limb_type>(static_cast<double_limb_type>(long_numerator - static_cast<double_limb_type>(static_cast<double_limb_type>(short_denominator) * hi_part)) << static_cast<unsigned>(std::numeric_limits<limb_type>::digits))
          );

        *remainder = static_cast<limb_type>(long_numerator >> static_cast<unsigned>(std::numeric_limits<limb_type>::digits));
      }
    }

    WIDE_INTEGER_NODISCARD WIDE_INTEGER_CONSTEXPR auto is_zero() const -> bool
    {
      auto it = values.cbegin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto)

      while((it != values.cend()) && (*it == static_cast<limb_type>(0U))) // NOLINT(altera-id-dependent-backward-branch)
      {
        ++it;
      }

      return (it == values.cend());
    }

    template<const bool RePhraseIsSigned = IsSigned,
             std::enable_if_t<(!RePhraseIsSigned)> const* = nullptr>
    static constexpr auto is_neg(const uintwide_t<Width2, LimbType, AllocatorType, RePhraseIsSigned>&) -> bool // NOLINT(hicpp-named-parameter,readability-named-parameter)
    {
      return false;
    }

    template<const bool RePhraseIsSigned = IsSigned,
             std::enable_if_t<RePhraseIsSigned> const* = nullptr>
    static constexpr auto is_neg(const uintwide_t<Width2, LimbType, AllocatorType, RePhraseIsSigned>& a) -> bool
    {
      return (static_cast<std::uint_fast8_t>(static_cast<std::uint_fast8_t>(a.values.back() >> static_cast<size_t>(std::numeric_limits<typename uintwide_t<Width2, LimbType, AllocatorType, RePhraseIsSigned>::limb_type>::digits - 1)) & 1U) != 0U);
    }

    static constexpr auto from_rep(const representation_type& other_rep) -> uintwide_t
    {
      // Create a factory-like object from the internal data representation.
      return uintwide_t(static_cast<const representation_type&>(other_rep));
    }

    static constexpr auto from_rep(representation_type&& other_rep) -> uintwide_t
    {
      // Create a factory-like object from the internal data representation.
      return uintwide_t(static_cast<representation_type&&>(other_rep));
    }

  private:
    representation_type values { };  // NOLINT(readability-identifier-naming)

    explicit constexpr uintwide_t(const representation_type& other_rep)
      : values(static_cast<const representation_type&>(other_rep)) { }

    explicit constexpr uintwide_t(representation_type&& other_rep)
      : values(static_cast<representation_type&&>(other_rep)) { }

    template<typename InputIteratorLeftType,
             typename InputIteratorRightType>
    static WIDE_INTEGER_CONSTEXPR auto compare_ranges(      InputIteratorLeftType  a,
                                                            InputIteratorRightType b,
                                                      const unsigned_fast_type     count) -> std::int_fast8_t
    {
      std::int_fast8_t n_return = 0;

      std::reverse_iterator<InputIteratorLeftType>  pa(a + count);
      std::reverse_iterator<InputIteratorRightType> pb(b + count);

      while(pa != std::reverse_iterator<InputIteratorLeftType>(a)) // NOLINT(altera-id-dependent-backward-branch)
      {
        using value_left_type =
          typename std::iterator_traits<InputIteratorLeftType>::value_type;

        const auto value_a = *pa++;
        const auto value_b = static_cast<value_left_type>(*pb++);

        if(value_a != value_b)
        {
          n_return = static_cast<std::int_fast8_t>((value_a > value_b) ? INT8_C(1) : INT8_C(-1));

          break;
        }
      }

      return n_return;
    }

    template<typename UnknownBuiltInIntegralType>
    struct digits_ratio
    {
      using local_unknown_builtin_integral_type  = UnknownBuiltInIntegralType;

      using local_unsigned_conversion_type =
        typename detail::uint_type_helper<
          std::numeric_limits<local_unknown_builtin_integral_type>::is_signed
            ? static_cast<size_t>(std::numeric_limits<local_unknown_builtin_integral_type>::digits + 1)
            : static_cast<size_t>(std::numeric_limits<local_unknown_builtin_integral_type>::digits + 0)>::exact_unsigned_type;

      static constexpr unsigned_fast_type value = 
        static_cast<unsigned_fast_type>(  std::numeric_limits<local_unsigned_conversion_type>::digits
                                        / std::numeric_limits<limb_type>::digits);

      template<typename InputIteratorLeft>
      static WIDE_INTEGER_CONSTEXPR auto extract(InputIteratorLeft p_limb, unsigned_fast_type limb_count) -> local_unknown_builtin_integral_type
      {
        using local_limb_type      = typename std::iterator_traits<InputIteratorLeft>::value_type;
        using left_difference_type = typename std::iterator_traits<InputIteratorLeft>::difference_type;

        auto u = static_cast<local_unsigned_conversion_type>(0U);

        constexpr auto shift_lim =
          static_cast<unsigned_fast_type>
          (
            std::numeric_limits<local_unsigned_conversion_type>::digits
          );

        for(auto   i = static_cast<unsigned_fast_type>(0U);
                   (    // NOLINT(altera-id-dependent-backward-branch)
                        (i < limb_count)
                     && (static_cast<unsigned_fast_type>(static_cast<unsigned_fast_type>(std::numeric_limits<local_limb_type>::digits) * i) < shift_lim)
                   );
                 ++i)
        {
          u =
            static_cast<local_unsigned_conversion_type>
            (
                u
              | static_cast<local_unsigned_conversion_type>(static_cast<local_unsigned_conversion_type>(*(p_limb + static_cast<left_difference_type>(i))) << static_cast<unsigned_fast_type>(static_cast<unsigned_fast_type>(std::numeric_limits<local_limb_type>::digits) * i))
            );
        }

        return static_cast<local_unknown_builtin_integral_type>(u);
      }
    };

    // Implement a function that extracts any built-in signed or unsigned integral type.
    template<typename UnknownBuiltInIntegralType,
             typename = std::enable_if_t<std::is_integral<UnknownBuiltInIntegralType>::value>>
    WIDE_INTEGER_NODISCARD WIDE_INTEGER_CONSTEXPR auto extract_builtin_integral_type() const -> UnknownBuiltInIntegralType
    {
      using local_unknown_integral_type = UnknownBuiltInIntegralType;
      using digits_ratio_type           = digits_ratio<local_unknown_integral_type>;

      const unsigned_fast_type ilim = (std::min)(static_cast<unsigned_fast_type>(digits_ratio_type::value),
                                                 static_cast<unsigned_fast_type>(values.size()));

      // Handle cases for which the input parameter is less wide
      // or equally as wide as the limb width or wider than the limb width.
      return ((digits_ratio_type::value < 2U)
               ? static_cast<local_unknown_integral_type>(*values.cbegin())
               : digits_ratio_type::extract(values.data(), ilim));
    }

    #if !defined(WIDE_INTEGER_DISABLE_FLOAT_INTEROP)
    // Implement a function that extracts any built-in floating-point type.
    template<typename FloatingPointType,
             typename = std::enable_if_t<std::is_floating_point<FloatingPointType>::value>>
    WIDE_INTEGER_NODISCARD WIDE_INTEGER_CONSTEXPR auto extract_builtin_floating_point_type() const -> FloatingPointType
    {
      using local_unsigned_wide_integer_type = uintwide_t<Width2, limb_type, AllocatorType, false>;
      using local_builtin_float_type         = FloatingPointType;

      const auto u_is_neg = is_neg(*this);

      const local_unsigned_wide_integer_type u((!u_is_neg) ? *this : -*this);

      const auto my_msb = static_cast<size_t>(msb(u));
      const auto ilim   = static_cast<size_t>
                          (
                             static_cast<size_t>(                     static_cast<size_t>(my_msb + static_cast<size_t>(1U)) / static_cast<size_t>(std::numeric_limits<limb_type>::digits))
                           + static_cast<size_t>((static_cast<size_t>(static_cast<size_t>(my_msb + static_cast<size_t>(1U)) % static_cast<size_t>(std::numeric_limits<limb_type>::digits)) != static_cast<size_t>(0U)) ? static_cast<size_t>(1U) : static_cast<size_t>(0U))
                          );

      auto a = static_cast<local_builtin_float_type>(0.0F);

      constexpr long double one_ldbl(1.0L);

      long double ldexp_runner(one_ldbl);

      for(auto i = static_cast<size_t>(0U); i < ilim; ++i) // NOLINT(altera-id-dependent-backward-branch)
      {
        auto ld      = static_cast<long double>(0.0L);
        auto lm_mask = static_cast<limb_type>(1ULL);

        for(auto j = static_cast<size_t>(0U); j < static_cast<size_t>(std::numeric_limits<limb_type>::digits); ++j)
        {
          if(static_cast<limb_type>(*(u.values.cbegin() + static_cast<size_t>(i)) & lm_mask) != static_cast<limb_type>(0U))
          {
            ld = static_cast<long double>(ld + ldexp_runner);
          }

          constexpr long double two_ldbl(2.0L);

          lm_mask      = static_cast<limb_type>  (lm_mask << 1U);
          ldexp_runner = static_cast<long double>(ldexp_runner * two_ldbl);
        }

        a += static_cast<local_builtin_float_type>(ld);
      }

      return static_cast<local_builtin_float_type>((!u_is_neg) ? a : -a);
    }
    #endif

    template<const size_t OtherWidth2>
    static WIDE_INTEGER_CONSTEXPR auto eval_mul_unary(      uintwide_t<OtherWidth2, LimbType, AllocatorType, IsSigned>& u,
                                                      const uintwide_t<OtherWidth2, LimbType, AllocatorType, IsSigned>& v,
                                                      std::enable_if_t<((OtherWidth2 / std::numeric_limits<LimbType>::digits) < number_of_limbs_karatsuba_threshold)>* p_nullparam = nullptr) -> void
    {
      static_cast<void>(p_nullparam == nullptr);

      // Unary multiplication function using schoolbook multiplication,
      // but we only need to retain the low half of the n*n algorithm.
      // In other words, this is an n*n->n bit multiplication.

      constexpr size_t local_number_of_limbs =
        uintwide_t<OtherWidth2, LimbType, AllocatorType, IsSigned>::number_of_limbs;

      representation_type result { };

      eval_multiply_n_by_n_to_lo_part(result.data(),
                                      u.values.data(),
                                      v.values.data(),
                                      local_number_of_limbs);

      std::copy(result.cbegin(),
                result.cbegin() + local_number_of_limbs,
                u.values.begin());
    }

    template<const size_t OtherWidth2>
    static WIDE_INTEGER_CONSTEXPR auto eval_mul_unary(      uintwide_t<OtherWidth2, LimbType, AllocatorType, IsSigned>& u,
                                                      const uintwide_t<OtherWidth2, LimbType, AllocatorType, IsSigned>& v,
                                                      std::enable_if_t<((OtherWidth2 / std::numeric_limits<LimbType>::digits) >= number_of_limbs_karatsuba_threshold)>* p_nullparam = nullptr) -> void
    {
      static_cast<void>(p_nullparam == nullptr);

      // Unary multiplication function using Karatsuba multiplication.

      constexpr size_t local_number_of_limbs =
        uintwide_t<OtherWidth2, LimbType, AllocatorType, IsSigned>::number_of_limbs;

      // TBD: Can use specialized allocator or memory pool for these arrays.
      // Good examples for this (both threaded as well as non-threaded)
      // can be found in the wide_decimal project.
      using result_array_type =
        typename std::conditional<std::is_same<AllocatorType, void>::value,
                                  detail::fixed_static_array <limb_type, number_of_limbs * 2U>,
                                  detail::fixed_dynamic_array<limb_type,
                                                              number_of_limbs * 2U,
                                                              typename std::allocator_traits<typename std::conditional<std::is_same<AllocatorType, void>::value,
                                                                                                                       std::allocator<void>,
                                                                                                                       AllocatorType>::type>::template rebind_alloc<limb_type>>>::type;

      using storage_array_type =
        typename std::conditional<std::is_same<AllocatorType, void>::value,
                                  detail::fixed_static_array <limb_type, number_of_limbs * 4U>,
                                  detail::fixed_dynamic_array<limb_type,
                                                              number_of_limbs * 4U,
                                                              typename std::allocator_traits<typename std::conditional<std::is_same<AllocatorType, void>::value,
                                                                                                                       std::allocator<void>,
                                                                                                                       AllocatorType>::type>::template rebind_alloc<limb_type>>>::type;

      result_array_type  result;
      storage_array_type t;

      eval_multiply_kara_n_by_n_to_2n(result.data(),
                                      u.values.data(),
                                      v.values.data(),
                                      local_number_of_limbs,
                                      t.data());

      std::copy(result.cbegin(),
                result.cbegin() + local_number_of_limbs,
                u.values.begin());
    }

    template<typename ResultIterator,
             typename InputIteratorLeft,
             typename InputIteratorRight>
    static WIDE_INTEGER_CONSTEXPR auto eval_add_n(      ResultIterator     r,
                                                        InputIteratorLeft  u,
                                                        InputIteratorRight v,
                                                  const unsigned_fast_type count,
                                                  const limb_type          carry_in = static_cast<limb_type>(0U)) -> limb_type
    {
      auto carry_out = static_cast<std::uint_fast8_t>(carry_in);

      static_assert
      (
           (std::numeric_limits<typename std::iterator_traits<ResultIterator>::value_type>::digits == std::numeric_limits<typename std::iterator_traits<InputIteratorLeft>::value_type>::digits)
        && (std::numeric_limits<typename std::iterator_traits<ResultIterator>::value_type>::digits == std::numeric_limits<typename std::iterator_traits<InputIteratorRight>::value_type>::digits),
        "Error: Internals require same widths for left-right-result limb_types at the moment"
      );

      using local_limb_type = typename std::iterator_traits<ResultIterator>::value_type;

      using local_double_limb_type =
        typename detail::uint_type_helper<static_cast<size_t>(std::numeric_limits<local_limb_type>::digits * 2)>::exact_unsigned_type;

      using result_difference_type = typename std::iterator_traits<ResultIterator>::difference_type;
      using left_difference_type   = typename std::iterator_traits<InputIteratorLeft>::difference_type;
      using right_difference_type  = typename std::iterator_traits<InputIteratorRight>::difference_type;

      for(auto i = static_cast<unsigned_fast_type>(0U); i < count; ++i)
      {
        const auto uv_as_ularge =
          static_cast<local_double_limb_type>(static_cast<local_double_limb_type>(static_cast<local_double_limb_type>(*(u + static_cast<left_difference_type>(i))) + *(v + static_cast<right_difference_type>(i))) + carry_out);

        carry_out = static_cast<std::uint_fast8_t>(detail::make_hi<local_limb_type>(uv_as_ularge));

        *(r + static_cast<result_difference_type>(i)) = static_cast<local_limb_type>(uv_as_ularge);
      }

      return static_cast<limb_type>(carry_out);
    }

    template<typename ResultIterator,
             typename InputIteratorLeft,
             typename InputIteratorRight>
    static WIDE_INTEGER_CONSTEXPR auto eval_subtract_n(      ResultIterator     r,
                                                             InputIteratorLeft  u,
                                                             InputIteratorRight v,
                                                       const unsigned_fast_type count,
                                                       const bool               has_borrow_in = false) -> bool
    {
      std::uint_fast8_t has_borrow_out = (has_borrow_in ? 1U : 0U);

      static_assert
      (
           (std::numeric_limits<typename std::iterator_traits<ResultIterator>::value_type>::digits == std::numeric_limits<typename std::iterator_traits<InputIteratorLeft>::value_type>::digits)
        && (std::numeric_limits<typename std::iterator_traits<ResultIterator>::value_type>::digits == std::numeric_limits<typename std::iterator_traits<InputIteratorRight>::value_type>::digits),
        "Error: Internals require same widths for left-right-result limb_types at the moment"
      );

      using local_limb_type = typename std::iterator_traits<ResultIterator>::value_type;

      using local_double_limb_type =
        typename detail::uint_type_helper<static_cast<size_t>(std::numeric_limits<local_limb_type>::digits * 2)>::exact_unsigned_type;

      using result_difference_type = typename std::iterator_traits<ResultIterator>::difference_type;
      using left_difference_type   = typename std::iterator_traits<InputIteratorLeft>::difference_type;
      using right_difference_type  = typename std::iterator_traits<InputIteratorRight>::difference_type;

      for(auto i = static_cast<unsigned_fast_type>(0U); i < count; ++i)
      {
        const auto uv_as_ularge = static_cast<local_double_limb_type>(static_cast<local_double_limb_type>(static_cast<local_double_limb_type>(*(u + static_cast<left_difference_type>(i))) - *(v + static_cast<right_difference_type>(i))) - has_borrow_out);

        has_borrow_out = (detail::make_hi<local_limb_type>(uv_as_ularge) != static_cast<local_limb_type>(0U)) ? 1U : 0U;

        *(r + static_cast<result_difference_type>(i)) = static_cast<local_limb_type>(uv_as_ularge);
      }

      return (has_borrow_out != 0U);
    }

    template<typename ResultIterator,
             typename InputIteratorLeft,
             typename InputIteratorRight,
             const size_t RePhraseWidth2 = Width2,
             std::enable_if_t<(uintwide_t<RePhraseWidth2, LimbType, AllocatorType, IsSigned>::number_of_limbs == 4U)> const* = nullptr>
    static WIDE_INTEGER_CONSTEXPR auto eval_multiply_n_by_n_to_lo_part(      ResultIterator     r,
                                                                             InputIteratorLeft  a,
                                                                             InputIteratorRight b,
                                                                       const unsigned_fast_type count) -> void
    {
      static_cast<void>(count);

      static_assert
      (
           (std::numeric_limits<typename std::iterator_traits<ResultIterator>::value_type>::digits == std::numeric_limits<typename std::iterator_traits<InputIteratorLeft>::value_type>::digits)
        && (std::numeric_limits<typename std::iterator_traits<ResultIterator>::value_type>::digits == std::numeric_limits<typename std::iterator_traits<InputIteratorRight>::value_type>::digits),
        "Error: Internals require same widths for left-right-result limb_types at the moment"
      );

      using local_limb_type = typename std::iterator_traits<ResultIterator>::value_type;

      using local_double_limb_type =
        typename detail::uint_type_helper<static_cast<size_t>(std::numeric_limits<local_limb_type>::digits * 2)>::exact_unsigned_type;

      using result_difference_type = typename std::iterator_traits<ResultIterator>::difference_type;
      using left_difference_type   = typename std::iterator_traits<InputIteratorLeft>::difference_type;
      using right_difference_type  = typename std::iterator_traits<InputIteratorRight>::difference_type;
      using result_value_type      = typename std::iterator_traits<ResultIterator>::value_type;

      // The algorithm has been derived from the polynomial multiplication.
      // After the multiplication terms of equal order are grouped
      // together and retained up to order(3). The carries from the
      // multiplications are included when adding up the terms.
      // The results of the intermediate multiplications are stored
      // in local variables in memory.

      //   Column[CoefficientList[Expand[(a0 + a1 x + a2 x^2 + a3 x^3) (b0 + b1 x + b2 x^2 + b3 x^3)], x]]
      //   a0b0
      //   a1b0 + a0b1
      //   a2b0 + a1b1 + a0b2
      //   a3b0 + a2b1 + a1b2 + a0b3

      // See also Wolfram Alpha at:
      // https://www.wolframalpha.com/input/?i=Column%5BCoefficientList%5B+++Expand%5B%28a0+%2B+a1+x+%2B+a2+x%5E2+%2B+a3+x%5E3%29+%28b0+%2B+b1+x+%2B+b2+x%5E2+%2B+b3+x%5E3%29%5D%2C++++x%5D%5D
      // ... and take the upper half of the pyramid.

      // Performance improvement:
      //   (old) kops_per_sec: 33173.50
      //   (new) kops_per_sec: 95069.43

      local_double_limb_type r1;
      local_double_limb_type r2;

      const auto a0b0 = static_cast<local_double_limb_type>(*(a + static_cast<left_difference_type>(0)) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(0))));
      const auto a0b1 = static_cast<local_double_limb_type>(*(a + static_cast<left_difference_type>(0)) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(1))));
      const auto a1b0 = static_cast<local_double_limb_type>(*(a + static_cast<left_difference_type>(1)) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(0))));
      const auto a1b1 = static_cast<local_double_limb_type>(*(a + static_cast<left_difference_type>(1)) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(1))));

      // One special case is considered, the case of multiplication
      // of the form BITS/2 * BITS/2 = BITS. In this case, the algorithm
      // can be significantly simplified by using only the 'lower-halves'
      // of the data.
      if(    (*(a + static_cast<left_difference_type>(2)) == 0U) && (*(b + static_cast<right_difference_type>(2)) == 0U)
          && (*(a + static_cast<left_difference_type>(3)) == 0U) && (*(b + static_cast<right_difference_type>(3)) == 0U))
      {
        r1    = static_cast<local_double_limb_type>
                (
                  static_cast<local_double_limb_type>
                  (
                    detail::make_hi<local_limb_type>(a0b0)
                  )
                  + detail::make_lo<local_limb_type>(a1b0)
                  + detail::make_lo<local_limb_type>(a0b1)
                )
                ;
        r2    = static_cast<local_double_limb_type>
                (
                  static_cast<local_double_limb_type>
                  (
                    detail::make_hi<local_limb_type>(r1)
                  )
                  + detail::make_lo<local_limb_type>(a1b1)
                  + detail::make_hi<local_limb_type>(a0b1)
                  + detail::make_hi<local_limb_type>(a1b0)
                )
                ;
        *(r + static_cast<result_difference_type>(3))
              = static_cast<result_value_type>
                (
                    detail::make_hi<local_limb_type>(r2)
                  + detail::make_hi<local_limb_type>(a1b1)
                )
                ;
      }
      else
      {
        const auto a0b2 = static_cast<local_double_limb_type>(*(a + static_cast<left_difference_type>(0)) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(2))));
        const auto a2b0 = static_cast<local_double_limb_type>(*(a + static_cast<left_difference_type>(2)) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(0))));

        r1    = static_cast<local_double_limb_type>
                (
                  static_cast<local_double_limb_type>
                  (
                    detail::make_hi<local_limb_type>(a0b0)
                  )
                  + detail::make_lo<local_limb_type>(a1b0)
                  + detail::make_lo<local_limb_type>(a0b1)
                )
                ;
        r2    = static_cast<local_double_limb_type>
                (
                  static_cast<local_double_limb_type>
                  (
                    detail::make_hi<local_limb_type>(r1)
                  )
                  + detail::make_lo<local_limb_type>(a2b0)
                  + detail::make_lo<local_limb_type>(a1b1)
                  + detail::make_lo<local_limb_type>(a0b2)
                  + detail::make_hi<local_limb_type>(a1b0)
                  + detail::make_hi<local_limb_type>(a0b1)
                )
                ;
        *(r + static_cast<result_difference_type>(3))
              = static_cast<result_value_type>
                (
                    detail::make_hi<local_limb_type>(r2)
                  + static_cast<local_limb_type>    (*(a + static_cast<left_difference_type>(3)) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(0))))
                  + static_cast<local_limb_type>    (*(a + static_cast<left_difference_type>(2)) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(1))))
                  + static_cast<local_limb_type>    (*(a + static_cast<left_difference_type>(1)) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(2))))
                  + static_cast<local_limb_type>    (*(a + static_cast<left_difference_type>(0)) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(3))))
                  + detail::make_hi<local_limb_type>(a2b0)
                  + detail::make_hi<local_limb_type>(a1b1)
                  + detail::make_hi<local_limb_type>(a0b2)
                )
                ;
      }

      *(r + static_cast<result_difference_type>(0)) = static_cast<local_limb_type>(a0b0);
      *(r + static_cast<result_difference_type>(1)) = static_cast<local_limb_type>(r1);
      *(r + static_cast<result_difference_type>(2)) = static_cast<local_limb_type>(r2);
    }

    #if defined(WIDE_INTEGER_HAS_MUL_8_BY_8_UNROLL)
    template<typename ResultIterator,
             typename InputIteratorLeft,
             typename InputIteratorRight,
             const size_t RePhraseWidth2 = Width2,
             std::enable_if_t<(uintwide_t<RePhraseWidth2, LimbType, AllocatorType, IsSigned>::number_of_limbs == static_cast<size_t>(UINT32_C(8)))> const* = nullptr>
    static WIDE_INTEGER_CONSTEXPR auto eval_multiply_n_by_n_to_lo_part(      ResultIterator     r,
                                                                             InputIteratorLeft  a,
                                                                             InputIteratorRight b,
                                                                       const unsigned_fast_type count) -> void
    {
      static_cast<void>(count);

      static_assert
      (
           (std::numeric_limits<typename std::iterator_traits<ResultIterator>::value_type>::digits == std::numeric_limits<typename std::iterator_traits<InputIteratorLeft>::value_type>::digits)
        && (std::numeric_limits<typename std::iterator_traits<ResultIterator>::value_type>::digits == std::numeric_limits<typename std::iterator_traits<InputIteratorRight>::value_type>::digits),
        "Error: Internals require same widths for left-right-result limb_types at the moment"
      );

      using local_limb_type = typename std::iterator_traits<ResultIterator>::value_type;

      using local_double_limb_type =
        typename detail::uint_type_helper<static_cast<size_t>(std::numeric_limits<local_limb_type>::digits * 2)>::exact_unsigned_type;

      using result_difference_type = typename std::iterator_traits<ResultIterator>::difference_type;
      using left_difference_type   = typename std::iterator_traits<InputIteratorLeft>::difference_type;
      using right_difference_type  = typename std::iterator_traits<InputIteratorRight>::difference_type;

      // The algorithm has been derived from the polynomial multiplication.
      // After the multiplication terms of equal order are grouped
      // together and retained up to order(3). The carries from the
      // multiplications are included when adding up the terms.
      // The results of the intermediate multiplications are stored
      // in local variables in memory.

      //   Column[CoefficientList[Expand[(a0 + a1 x + a2 x^2 + a3 x^3 + a4 x^4 + a5 x^5 + a6 x^6 + a7 x^7) (b0 + b1 x + b2 x^2 + b3 x^3 + b4 x^4 + b5 x^5 + b6 x^6 + b7 x^7)], x]]
      //   a0b0
      //   a1b0 + a0b1
      //   a2b0 + a1b1 + a0b2
      //   a3b0 + a2b1 + a1b2 + a0b3
      //   a4b0 + a3b1 + a2b2 + a1b3 + a0b4
      //   a5b0 + a4b1 + a3b2 + a2b3 + a1b4 + a0b5
      //   a6b0 + a5b1 + a4b2 + a3b3 + a2b4 + a1b5 + a0b6
      //   a7b0 + a6b1 + a5b2 + a4b3 + a3b4 + a2b5 + a1b6 + a0b7

      // See also Wolfram Alpha at:
      // https://www.wolframalpha.com/input/?i=Column%5BCoefficientList%5B+++Expand%5B%28a0+%2B+a1+x+%2B+a2+x%5E2+%2B+a3+x%5E3%29+%28b0+%2B+b1+x+%2B+b2+x%5E2+%2B+b3+x%5E3%29%5D%2C++++x%5D%5D
      // ... and take the upper half of the pyramid.

      const local_double_limb_type a0b0 = *(a + static_cast<left_difference_type>(0)) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(0)));

      const local_double_limb_type a1b0 = *(a + static_cast<left_difference_type>(1)) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(0)));
      const local_double_limb_type a0b1 = *(a + static_cast<left_difference_type>(0)) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(1)));

      const local_double_limb_type a2b0 = *(a + static_cast<left_difference_type>(2)) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(0)));
      const local_double_limb_type a1b1 = *(a + static_cast<left_difference_type>(1)) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(1)));
      const local_double_limb_type a0b2 = *(a + static_cast<left_difference_type>(0)) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(2)));

      const local_double_limb_type a3b0 = *(a + static_cast<left_difference_type>(3)) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(0)));
      const local_double_limb_type a2b1 = *(a + static_cast<left_difference_type>(2)) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(1)));
      const local_double_limb_type a1b2 = *(a + static_cast<left_difference_type>(1)) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(2)));
      const local_double_limb_type a0b3 = *(a + static_cast<left_difference_type>(0)) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(3)));

      const local_double_limb_type a3b1 = *(a + static_cast<left_difference_type>(3)) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(1)));
      const local_double_limb_type a2b2 = *(a + static_cast<left_difference_type>(2)) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(2)));
      const local_double_limb_type a1b3 = *(a + static_cast<left_difference_type>(1)) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(3)));

      const local_double_limb_type a3b2 = *(a + static_cast<left_difference_type>(3)) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(2)));
      const local_double_limb_type a2b3 = *(a + static_cast<left_difference_type>(2)) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(3)));

      const local_double_limb_type a3b3 = *(a + static_cast<left_difference_type>(3)) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(3)));

            local_double_limb_type rd1;
            local_double_limb_type rd2;
            local_double_limb_type rd3;
            local_double_limb_type rd4;
            local_double_limb_type rd5;
            local_double_limb_type rd6;

      // One special case is considered, the case of multiplication
      // of the form BITS/2 * BITS/2 = BITS. In this case, the algorithm
      // can be significantly simplified by using only the 'lower-halves'
      // of the data.
      if(    (*(a + static_cast<left_difference_type>(INT32_C(7))) == 0U) && (*(b + static_cast<right_difference_type>(INT32_C(7))) == 0U)
          && (*(a + static_cast<left_difference_type>(INT32_C(6))) == 0U) && (*(b + static_cast<right_difference_type>(INT32_C(6))) == 0U)
          && (*(a + static_cast<left_difference_type>(INT32_C(5))) == 0U) && (*(b + static_cast<right_difference_type>(INT32_C(5))) == 0U)
          && (*(a + static_cast<left_difference_type>(INT32_C(4))) == 0U) && (*(b + static_cast<right_difference_type>(INT32_C(4))) == 0U))
      {
        rd1   = static_cast<local_double_limb_type>
                (
                  detail::make_hi<local_limb_type>(a0b0)
                )
                + detail::make_lo<local_limb_type>(a1b0)
                + detail::make_lo<local_limb_type>(a0b1)
                ;

        rd2   = static_cast<local_double_limb_type>
                (
                  detail::make_hi<local_limb_type>(rd1)
                )
                + detail::make_lo<local_limb_type>(a2b0)
                + detail::make_lo<local_limb_type>(a1b1)
                + detail::make_lo<local_limb_type>(a0b2)
                + detail::make_hi<local_limb_type>(a1b0)
                + detail::make_hi<local_limb_type>(a0b1)
                ;

        rd3   = static_cast<local_double_limb_type>
                (
                  detail::make_hi<local_limb_type>(rd2)
                )
                + detail::make_lo<local_limb_type>(a3b0)
                + detail::make_lo<local_limb_type>(a2b1)
                + detail::make_lo<local_limb_type>(a1b2)
                + detail::make_lo<local_limb_type>(a0b3)
                + detail::make_hi<local_limb_type>(a2b0)
                + detail::make_hi<local_limb_type>(a1b1)
                + detail::make_hi<local_limb_type>(a0b2)
                ;

        rd4   = static_cast<local_double_limb_type>
                (
                  detail::make_hi<local_limb_type>(rd3)
                )
                + detail::make_lo<local_limb_type>(a3b1)
                + detail::make_lo<local_limb_type>(a2b2)
                + detail::make_lo<local_limb_type>(a1b3)
                + detail::make_hi<local_limb_type>(a3b0)
                + detail::make_hi<local_limb_type>(a2b1)
                + detail::make_hi<local_limb_type>(a1b2)
                + detail::make_hi<local_limb_type>(a0b3)
                ;

        rd5   = static_cast<local_double_limb_type>
                (
                  detail::make_hi<local_limb_type>(rd4)
                )
                + detail::make_lo<local_limb_type>(a3b2)
                + detail::make_lo<local_limb_type>(a2b3)
                + detail::make_hi<local_limb_type>(a3b1)
                + detail::make_hi<local_limb_type>(a2b2)
                + detail::make_hi<local_limb_type>(a1b3)
                ;

        rd6   = static_cast<local_double_limb_type>
                (
                  detail::make_hi<local_limb_type>(rd5)
                )
                + detail::make_lo<local_limb_type>(a3b3)
                + detail::make_hi<local_limb_type>(a3b2)
                + detail::make_hi<local_limb_type>(a2b3)
                ;

        *(r + static_cast<result_difference_type>(INT32_C(7)))
              = static_cast<local_limb_type>
                (
                    detail::make_hi<local_limb_type>(rd6)
                  + detail::make_hi<local_limb_type>(a3b3)
                )
                ;
      }
      else
      {
        const local_double_limb_type a4b0 = *(a + static_cast<left_difference_type>(INT32_C(4))) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(INT32_C(0))));
        const local_double_limb_type a0b4 = *(a + static_cast<left_difference_type>(INT32_C(0))) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(INT32_C(4))));

        const local_double_limb_type a5b0 = *(a + static_cast<left_difference_type>(INT32_C(5))) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(INT32_C(0))));
        const local_double_limb_type a4b1 = *(a + static_cast<left_difference_type>(INT32_C(4))) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(INT32_C(1))));

        const local_double_limb_type a1b4 = *(a + static_cast<left_difference_type>(INT32_C(1))) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(INT32_C(4))));
        const local_double_limb_type a0b5 = *(a + static_cast<left_difference_type>(INT32_C(0))) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(INT32_C(5))));

        const local_double_limb_type a6b0 = *(a + static_cast<left_difference_type>(INT32_C(6))) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(INT32_C(0))));
        const local_double_limb_type a5b1 = *(a + static_cast<left_difference_type>(INT32_C(5))) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(INT32_C(1))));

        const local_double_limb_type a4b2 = *(a + static_cast<left_difference_type>(INT32_C(4))) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(INT32_C(2))));
        const local_double_limb_type a2b4 = *(a + static_cast<left_difference_type>(INT32_C(2))) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(INT32_C(4))));

        const local_double_limb_type a1b5 = *(a + static_cast<left_difference_type>(INT32_C(1))) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(INT32_C(5))));
        const local_double_limb_type a0b6 = *(a + static_cast<left_difference_type>(INT32_C(0))) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(INT32_C(6))));

        rd1   = static_cast<local_double_limb_type>
                (
                  detail::make_hi<local_limb_type>(a0b0)
                )
                + detail::make_lo<local_limb_type>(a1b0)
                + detail::make_lo<local_limb_type>(a0b1)
                ;

        rd2   = static_cast<local_double_limb_type>
                (
                  detail::make_hi<local_limb_type>(rd1)
                )
                + detail::make_lo<local_limb_type>(a2b0)
                + detail::make_lo<local_limb_type>(a1b1)
                + detail::make_lo<local_limb_type>(a0b2)
                + detail::make_hi<local_limb_type>(a1b0)
                + detail::make_hi<local_limb_type>(a0b1)
                ;

        rd3   = static_cast<local_double_limb_type>
                (
                  detail::make_hi<local_limb_type>(rd2)
                )
                + detail::make_lo<local_limb_type>(a3b0)
                + detail::make_lo<local_limb_type>(a2b1)
                + detail::make_lo<local_limb_type>(a1b2)
                + detail::make_lo<local_limb_type>(a0b3)
                + detail::make_hi<local_limb_type>(a2b0)
                + detail::make_hi<local_limb_type>(a1b1)
                + detail::make_hi<local_limb_type>(a0b2)
                ;

        rd4   = static_cast<local_double_limb_type>
                (
                  detail::make_hi<local_limb_type>(rd3)
                )
                + detail::make_lo<local_limb_type>(a4b0)
                + detail::make_lo<local_limb_type>(a3b1)
                + detail::make_lo<local_limb_type>(a2b2)
                + detail::make_lo<local_limb_type>(a1b3)
                + detail::make_lo<local_limb_type>(a0b4)
                + detail::make_hi<local_limb_type>(a3b0)
                + detail::make_hi<local_limb_type>(a2b1)
                + detail::make_hi<local_limb_type>(a1b2)
                + detail::make_hi<local_limb_type>(a0b3)
                ;

        rd5   = static_cast<local_double_limb_type>
                (
                  detail::make_hi<local_limb_type>(rd4)
                )
                + detail::make_lo<local_limb_type>(a5b0)
                + detail::make_lo<local_limb_type>(a4b1)
                + detail::make_lo<local_limb_type>(a3b2)
                + detail::make_lo<local_limb_type>(a2b3)
                + detail::make_lo<local_limb_type>(a1b4)
                + detail::make_lo<local_limb_type>(a0b5)
                + detail::make_hi<local_limb_type>(a4b0)
                + detail::make_hi<local_limb_type>(a3b1)
                + detail::make_hi<local_limb_type>(a2b2)
                + detail::make_hi<local_limb_type>(a1b3)
                + detail::make_hi<local_limb_type>(a0b4)
                ;

        rd6   = static_cast<local_double_limb_type>
                (
                  detail::make_hi<local_limb_type>(rd5)
                )
                + detail::make_lo<local_limb_type>(a6b0)
                + detail::make_lo<local_limb_type>(a5b1)
                + detail::make_lo<local_limb_type>(a4b2)
                + detail::make_lo<local_limb_type>(a3b3)
                + detail::make_lo<local_limb_type>(a2b4)
                + detail::make_lo<local_limb_type>(a1b5)
                + detail::make_lo<local_limb_type>(a0b6)
                + detail::make_hi<local_limb_type>(a5b0)
                + detail::make_hi<local_limb_type>(a4b1)
                + detail::make_hi<local_limb_type>(a3b2)
                + detail::make_hi<local_limb_type>(a2b3)
                + detail::make_hi<local_limb_type>(a1b4)
                + detail::make_hi<local_limb_type>(a0b5)
                ;

        *(r + static_cast<result_difference_type>(INT32_C(7)))
              = static_cast<local_limb_type>
                (
                    detail::make_hi<local_limb_type>(rd6)
                  + static_cast<local_limb_type>    (*(a + static_cast<left_difference_type>(INT32_C(7))) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(INT32_C(0)))))
                  + static_cast<local_limb_type>    (*(a + static_cast<left_difference_type>(INT32_C(6))) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(INT32_C(1)))))
                  + static_cast<local_limb_type>    (*(a + static_cast<left_difference_type>(INT32_C(5))) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(INT32_C(2)))))
                  + static_cast<local_limb_type>    (*(a + static_cast<left_difference_type>(INT32_C(4))) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(INT32_C(3)))))
                  + static_cast<local_limb_type>    (*(a + static_cast<left_difference_type>(INT32_C(3))) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(INT32_C(4)))))
                  + static_cast<local_limb_type>    (*(a + static_cast<left_difference_type>(INT32_C(2))) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(INT32_C(5)))))
                  + static_cast<local_limb_type>    (*(a + static_cast<left_difference_type>(INT32_C(1))) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(INT32_C(6)))))
                  + static_cast<local_limb_type>    (*(a + static_cast<left_difference_type>(INT32_C(0))) * static_cast<local_double_limb_type>(*(b + static_cast<right_difference_type>(INT32_C(7)))))
                  + detail::make_hi<local_limb_type>(a6b0)
                  + detail::make_hi<local_limb_type>(a5b1)
                  + detail::make_hi<local_limb_type>(a4b2)
                  + detail::make_hi<local_limb_type>(a3b3)
                  + detail::make_hi<local_limb_type>(a2b4)
                  + detail::make_hi<local_limb_type>(a1b5)
                  + detail::make_hi<local_limb_type>(a0b6)
                )
                ;
      }

      *(r + static_cast<result_difference_type>(INT32_C(0))) = static_cast<local_limb_type>(a0b0);
      *(r + static_cast<result_difference_type>(INT32_C(1))) = static_cast<local_limb_type>(rd1);
      *(r + static_cast<result_difference_type>(INT32_C(2))) = static_cast<local_limb_type>(rd2);
      *(r + static_cast<result_difference_type>(INT32_C(3))) = static_cast<local_limb_type>(rd3);
      *(r + static_cast<result_difference_type>(INT32_C(4))) = static_cast<local_limb_type>(rd4);
      *(r + static_cast<result_difference_type>(INT32_C(5))) = static_cast<local_limb_type>(rd5);
      *(r + static_cast<result_difference_type>(INT32_C(6))) = static_cast<local_limb_type>(rd6);
    }
    #endif

    template<typename ResultIterator,
             typename InputIteratorLeft,
             typename InputIteratorRight,
             const size_t RePhraseWidth2 = Width2,
             std::enable_if_t<(   (uintwide_t<RePhraseWidth2, LimbType, AllocatorType>::number_of_limbs != static_cast<size_t>(UINT32_C(4)))
    #if defined(WIDE_INTEGER_HAS_MUL_8_BY_8_UNROLL)
                               && (uintwide_t<RePhraseWidth2, LimbType, AllocatorType>::number_of_limbs != static_cast<size_t>(UINT32_C(8)))
    #endif
                              )> const* = nullptr>
    static WIDE_INTEGER_CONSTEXPR auto eval_multiply_n_by_n_to_lo_part(      ResultIterator     r,
                                                                             InputIteratorLeft  a,
                                                                             InputIteratorRight b,
                                                                       const unsigned_fast_type count) -> void
    {
      static_assert
      (
           (std::numeric_limits<typename std::iterator_traits<ResultIterator>::value_type>::digits == std::numeric_limits<typename std::iterator_traits<InputIteratorLeft>::value_type>::digits)
        && (std::numeric_limits<typename std::iterator_traits<ResultIterator>::value_type>::digits == std::numeric_limits<typename std::iterator_traits<InputIteratorRight>::value_type>::digits),
        "Error: Internals require same widths for left-right-result limb_types at the moment"
      );

      using local_limb_type = typename std::iterator_traits<ResultIterator>::value_type;

      using local_double_limb_type =
        typename detail::uint_type_helper<static_cast<size_t>(std::numeric_limits<local_limb_type>::digits * 2)>::exact_unsigned_type;

      using result_difference_type = typename std::iterator_traits<ResultIterator>::difference_type;
      using left_difference_type   = typename std::iterator_traits<InputIteratorLeft>::difference_type;
      using right_difference_type  = typename std::iterator_traits<InputIteratorRight>::difference_type;

      std::fill_n(r, count, static_cast<local_limb_type>(0U));

      for(auto i = static_cast<unsigned_fast_type>(0U); i < count; ++i)
      {
        if(*(a + static_cast<left_difference_type>(i)) != static_cast<local_limb_type>(0U))
        {
          local_double_limb_type carry = 0U;

          for(auto j = static_cast<unsigned_fast_type>(0U); j < static_cast<unsigned_fast_type>(count - i); ++j)
          {
            const auto i_plus_j = static_cast<result_difference_type>(i + j);

            carry = static_cast<local_double_limb_type>(carry + static_cast<local_double_limb_type>(static_cast<local_double_limb_type>(*(a + static_cast<left_difference_type>(i))) * *(b + static_cast<right_difference_type>(j))));
            carry = static_cast<local_double_limb_type>(carry + *(r + i_plus_j));

            *(r + i_plus_j) = static_cast<local_limb_type>(carry);
            carry           = detail::make_hi<local_limb_type>(carry);
          }
        }
      }
    }

    template<typename ResultIterator,
             typename InputIteratorLeft,
             typename InputIteratorRight>
    static WIDE_INTEGER_CONSTEXPR auto eval_multiply_n_by_n_to_2n(      ResultIterator     r,
                                                                        InputIteratorLeft  a,
                                                                        InputIteratorRight b,
                                                                  const unsigned_fast_type count) -> void
    {
      static_assert
      (
           (std::numeric_limits<typename std::iterator_traits<ResultIterator>::value_type>::digits == std::numeric_limits<typename std::iterator_traits<InputIteratorLeft>::value_type>::digits)
        && (std::numeric_limits<typename std::iterator_traits<ResultIterator>::value_type>::digits == std::numeric_limits<typename std::iterator_traits<InputIteratorRight>::value_type>::digits),
        "Error: Internals require same widths for left-right-result limb_types at the moment"
      );

      using local_limb_type = typename std::iterator_traits<ResultIterator>::value_type;

      using local_double_limb_type =
        typename detail::uint_type_helper<static_cast<size_t>(std::numeric_limits<local_limb_type>::digits * 2)>::exact_unsigned_type;

      using result_difference_type = typename std::iterator_traits<ResultIterator>::difference_type;
      using left_difference_type   = typename std::iterator_traits<InputIteratorLeft>::difference_type;
      using right_difference_type  = typename std::iterator_traits<InputIteratorRight>::difference_type;

      std::fill_n(r, (count * 2U), static_cast<local_limb_type>(0U));

      for(auto i = static_cast<unsigned_fast_type>(0U); i < count; ++i)
      {
        if(*(a + static_cast<left_difference_type>(i)) != static_cast<local_limb_type>(0U))
        {
          unsigned_fast_type j = 0U;

          local_double_limb_type carry = 0U;

          for( ; j < count; ++j)
          {
            const auto i_plus_j =
              static_cast<result_difference_type>
              (
                static_cast<result_difference_type>(i) + static_cast<result_difference_type>(j)
              );

            carry = static_cast<local_double_limb_type>(carry + static_cast<local_double_limb_type>(static_cast<local_double_limb_type>(*(a + static_cast<left_difference_type>(i))) * *(b + static_cast<right_difference_type>(j))));
            carry = static_cast<local_double_limb_type>(carry + *(r + i_plus_j));

            *(r + i_plus_j) = static_cast<local_limb_type>(carry);
            carry           = detail::make_hi<local_limb_type>(carry);
          }

          {
            const auto i_plus_count =
              static_cast<result_difference_type>
              (
                static_cast<result_difference_type>(i) + static_cast<result_difference_type>(count)
              );

            *(r + i_plus_count) = static_cast<local_limb_type>(carry);
          }
        }
      }
    }

    template<typename ResultIterator,
             typename InputIteratorLeft>
    static WIDE_INTEGER_CONSTEXPR auto eval_multiply_1d(      ResultIterator                                               r,
                                                              InputIteratorLeft                                            a,
                                                        const typename std::iterator_traits<InputIteratorLeft>::value_type b,
                                                        const unsigned_fast_type                                           count) -> limb_type
    {
      static_assert
      (
        (std::numeric_limits<typename std::iterator_traits<ResultIterator>::value_type>::digits == std::numeric_limits<typename std::iterator_traits<InputIteratorLeft>::value_type>::digits),
        "Error: Internals require same widths for left-right-result limb_types at the moment"
      );

      using local_limb_type = typename std::iterator_traits<ResultIterator>::value_type;

      using local_double_limb_type =
        typename detail::uint_type_helper<static_cast<size_t>(std::numeric_limits<local_limb_type>::digits * 2)>::exact_unsigned_type;

      using result_difference_type = typename std::iterator_traits<ResultIterator>::difference_type;
      using left_difference_type   = typename std::iterator_traits<InputIteratorLeft>::difference_type;

      local_double_limb_type carry = 0U;

      if(b == 0U)
      {
        std::fill(r, r + count, static_cast<limb_type>(0U));
      }
      else
      {
        for(auto i = static_cast<unsigned_fast_type>(0U) ; i < count; ++i)
        {
          carry = static_cast<local_double_limb_type>(carry + static_cast<local_double_limb_type>(static_cast<local_double_limb_type>(*(a + static_cast<left_difference_type>(i))) * b));

          *(r + static_cast<result_difference_type>(i)) = static_cast<local_limb_type>(carry);
          carry                                         = detail::make_hi<local_limb_type>(carry);
        }
      }

      return static_cast<local_limb_type>(carry);
    }

    template<typename InputIteratorLeft>
    static WIDE_INTEGER_CONSTEXPR
    auto eval_multiply_kara_propagate_carry(      InputIteratorLeft                                            t,
                                            const unsigned_fast_type                                           n,
                                            const typename std::iterator_traits<InputIteratorLeft>::value_type carry) -> void
    {
      using local_limb_type = typename std::iterator_traits<InputIteratorLeft>::value_type;

      using local_double_limb_type =
        typename detail::uint_type_helper<static_cast<size_t>(std::numeric_limits<local_limb_type>::digits * 2)>::exact_unsigned_type;

      using left_difference_type   = typename std::iterator_traits<InputIteratorLeft>::difference_type;

      unsigned_fast_type i = 0U;

      local_limb_type carry_out = carry;

      while((i < n) && (carry_out != static_cast<local_limb_type>(0U))) // NOLINT(altera-id-dependent-backward-branch)
      {
        const local_double_limb_type uv_as_ularge = static_cast<local_double_limb_type>(*(t + static_cast<left_difference_type>(i))) + carry_out;

        carry_out = detail::make_hi<local_limb_type>(uv_as_ularge);

        *(t + static_cast<left_difference_type>(i)) = static_cast<local_limb_type>(uv_as_ularge);

        ++i;
      }
    }

    template<typename InputIteratorLeft>
    static WIDE_INTEGER_CONSTEXPR
    auto eval_multiply_kara_propagate_borrow(      InputIteratorLeft  t,
                                             const unsigned_fast_type n,
                                             const bool               has_borrow) -> void
    {
      using local_limb_type = typename std::iterator_traits<InputIteratorLeft>::value_type;

      using local_double_limb_type =
        typename detail::uint_type_helper<static_cast<size_t>(std::numeric_limits<local_limb_type>::digits * 2)>::exact_unsigned_type;

      using left_difference_type   = typename std::iterator_traits<InputIteratorLeft>::difference_type;

      unsigned_fast_type i = 0U;

      bool has_borrow_out = has_borrow;

      while((i < n) && has_borrow_out) // NOLINT(altera-id-dependent-backward-branch)
      {
        auto uv_as_ularge = static_cast<local_double_limb_type>(*(t + static_cast<left_difference_type>(i)));

        if(has_borrow_out)
        {
          --uv_as_ularge;
        }

        has_borrow_out = (detail::make_hi<local_limb_type>(uv_as_ularge) != static_cast<local_limb_type>(0U));

        *(t + static_cast<left_difference_type>(i)) = static_cast<local_limb_type>(uv_as_ularge);

        ++i;
      }
    }

    template<typename ResultIterator,
             typename InputIteratorLeft,
             typename InputIteratorRight,
             typename InputIteratorTemp>
    static WIDE_INTEGER_CONSTEXPR
    auto eval_multiply_kara_n_by_n_to_2n(      ResultIterator     r, // NOLINT(misc-no-recursion)
                                         const InputIteratorLeft  a,
                                         const InputIteratorRight b,
                                         const unsigned_fast_type n,
                                               InputIteratorTemp  t) -> void
    {
      if(n <= static_cast<unsigned_fast_type>(UINT32_C(48)))
      {
        static_cast<void>(t);

        eval_multiply_n_by_n_to_2n(r, a, b, n);
      }
      else
      {
        static_assert
        (
             (std::numeric_limits<typename std::iterator_traits<ResultIterator>::value_type>::digits == std::numeric_limits<typename std::iterator_traits<InputIteratorLeft>::value_type>::digits)
          && (std::numeric_limits<typename std::iterator_traits<ResultIterator>::value_type>::digits == std::numeric_limits<typename std::iterator_traits<InputIteratorRight>::value_type>::digits)
          && (std::numeric_limits<typename std::iterator_traits<ResultIterator>::value_type>::digits == std::numeric_limits<typename std::iterator_traits<InputIteratorTemp>::value_type>::digits),
          "Error: Internals require same widths for left-right-result limb_types at the moment"
        );

        using local_limb_type = typename std::iterator_traits<ResultIterator>::value_type;

        using result_difference_type = typename std::iterator_traits<ResultIterator>::difference_type;
        using left_difference_type   = typename std::iterator_traits<InputIteratorLeft>::difference_type;
        using right_difference_type  = typename std::iterator_traits<InputIteratorRight>::difference_type;
        using temp_difference_type   = typename std::iterator_traits<InputIteratorTemp>::difference_type;

        // Based on "Algorithm 1.3 KaratsubaMultiply", Sect. 1.3.2, page 5
        // of R.P. Brent and P. Zimmermann, "Modern Computer Arithmetic",
        // Cambridge University Press (2011).

        // The Karatsuba multipliation computes the product of u*v as:
        // [b^N + b^(N/2)] a1*b1 + [b^(N/2)](a1 - a0)(b0 - b1) + [b^(N/2) + 1] a0*b0

        // Here we visualize u and v in two components 0,1 corresponding
        // to the high and low order parts, respectively.

        // Step 1
        // Calculate a1*b1 and store it in the upper part of r.
        // Calculate a0*b0 and store it in the lower part of r.
        // copy r to t0.

        // Step 2
        // Add a1*b1 (which is t2) to the middle two-quarters of r (which is r1)
        // Add a0*b0 (which is t0) to the middle two-quarters of r (which is r1)

        // Step 3
        // Calculate |a1-a0| in t0 and note the sign (i.e., the borrow flag)

        // Step 4
        // Calculate |b0-b1| in t1 and note the sign (i.e., the borrow flag)

        // Step 5
        // Call kara mul to calculate |a1-a0|*|b0-b1| in (t2),
        // while using temporary storage in t4 along the way.

        // Step 6
        // Check the borrow signs. If a1-a0 and b0-b1 have the same signs,
        // then add |a1-a0|*|b0-b1| to r1, otherwise subtract it from r1.

        const auto nh = static_cast<unsigned_fast_type>(n / 2U);

        const InputIteratorLeft   a0 = a + static_cast<left_difference_type>(0);
        const InputIteratorLeft   a1 = a + static_cast<left_difference_type>(nh);

        const InputIteratorRight  b0 = b + static_cast<right_difference_type>(0);
        const InputIteratorRight  b1 = b + static_cast<right_difference_type>(nh);

              ResultIterator      r0 = r + static_cast<result_difference_type>(0);
              ResultIterator      r1 = r + static_cast<result_difference_type>(nh);
              ResultIterator      r2 = r + static_cast<result_difference_type>(n);
              ResultIterator      r3 = r + static_cast<result_difference_type>(static_cast<result_difference_type>(n) + static_cast<result_difference_type>(nh));

              InputIteratorTemp   t0 = t + static_cast<temp_difference_type>(0);
              InputIteratorTemp   t1 = t + static_cast<temp_difference_type>(nh);
              InputIteratorTemp   t2 = t + static_cast<temp_difference_type>(n);
              InputIteratorTemp   t4 = t + static_cast<temp_difference_type>(static_cast<result_difference_type>(n) + static_cast<result_difference_type>(n));

        // Step 1
        //   a1*b1 -> r2
        //   a0*b0 -> r0
        //   r -> t0
        eval_multiply_kara_n_by_n_to_2n(r2, a1, b1, nh, t0);
        eval_multiply_kara_n_by_n_to_2n(r0, a0, b0, nh, t0);
        std::copy(r0, r0 + static_cast<result_difference_type>(static_cast<result_difference_type>(n) * static_cast<result_difference_type>(2U)), t0);

        local_limb_type carry;

        // Step 2
        //   r1 += a1*b1
        //   r1 += a0*b0
        carry = eval_add_n(r1, r1, t2, n);
        eval_multiply_kara_propagate_carry(r3, nh, carry);
        carry = eval_add_n(r1, r1, t0, n);
        eval_multiply_kara_propagate_carry(r3, nh, carry);

        // Step 3
        //   |a1-a0| -> t0
        const std::int_fast8_t cmp_result_a1a0 = compare_ranges(a1, a0, nh);

        if(cmp_result_a1a0 == 1)
        {
          static_cast<void>(eval_subtract_n(t0, a1, a0, nh));
        }
        else if(cmp_result_a1a0 == -1)
        {
          static_cast<void>(eval_subtract_n(t0, a0, a1, nh));
        }

        // Step 4
        //   |b0-b1| -> t1
        const std::int_fast8_t cmp_result_b0b1 = compare_ranges(b0, b1, nh);

        if(cmp_result_b0b1 == 1)
        {
          static_cast<void>(eval_subtract_n(t1, b0, b1, nh));
        }
        else if(cmp_result_b0b1 == -1)
        {
          static_cast<void>(eval_subtract_n(t1, b1, b0, nh));
        }

        // Step 5
        //   |a1-a0|*|b0-b1| -> t2
        eval_multiply_kara_n_by_n_to_2n(t2, t0, t1, nh, t4);

        // Step 6
        //   either r1 += |a1-a0|*|b0-b1|
        //   or     r1 -= |a1-a0|*|b0-b1|
        if((cmp_result_a1a0 * cmp_result_b0b1) == 1)
        {
          carry = eval_add_n(r1, r1, t2, n);

          eval_multiply_kara_propagate_carry(r3, nh, carry);
        }
        else if((cmp_result_a1a0 * cmp_result_b0b1) == -1)
        {
          const bool has_borrow = eval_subtract_n(r1, r1, t2, n);

          eval_multiply_kara_propagate_borrow(r3, nh, has_borrow);
        }
      }
    }

    WIDE_INTEGER_CONSTEXPR auto eval_divide_knuth(const uintwide_t& other,
                                                        uintwide_t* remainder = nullptr) -> void
    {
      // Use Knuth's long division algorithm.
      // The loop-ordering of indices in Knuth's original
      // algorithm has been reversed due to the data format
      // used here. Several optimizations and combinations
      // of logic have been carried out in the source code.

      // See also:
      // D.E. Knuth, "The Art of Computer Programming, Volume 2:
      // Seminumerical Algorithms", Addison-Wesley (1998),
      // Section 4.3.1 Algorithm D and Exercise 16.

      using local_uint_index_type = unsigned_fast_type;

      auto u_offset = static_cast<local_uint_index_type>(0U);
      auto v_offset = static_cast<local_uint_index_type>(0U);

      // Compute the offsets for u and v.
      for(auto i = static_cast<local_uint_index_type>(0U); (i < static_cast<local_uint_index_type>(number_of_limbs)) && (*(      values.cbegin() + static_cast<size_t>(static_cast<local_uint_index_type>(number_of_limbs - 1U) - i)) == static_cast<limb_type>(0U)); ++i) { ++u_offset; } // NOLINT(altera-id-dependent-backward-branch)
      for(auto i = static_cast<local_uint_index_type>(0U); (i < static_cast<local_uint_index_type>(number_of_limbs)) && (*(other.values.cbegin() + static_cast<size_t>(static_cast<local_uint_index_type>(number_of_limbs - 1U) - i)) == static_cast<limb_type>(0U)); ++i) { ++v_offset; } // NOLINT(altera-id-dependent-backward-branch)

      if(v_offset == static_cast<local_uint_index_type>(number_of_limbs))
      {
        // The denominator is zero. Set the maximum value and return.
        // This also catches (0 / 0) and sets the maximum value for it.
        operator=(limits_helper_max(IsSigned));

        if(remainder != nullptr)
        {
          *remainder = uintwide_t(static_cast<std::uint8_t>(0U));
        }
      }
      else if(u_offset == static_cast<local_uint_index_type>(number_of_limbs))
      {
        // The numerator is zero. Do nothing and return.

        if(remainder != nullptr)
        {
          *remainder = uintwide_t(static_cast<std::uint8_t>(0U));
        }
      }
      else
      {
        const auto result_of_compare_left_with_right = compare(other);

        const bool left_is_less_than_right = (result_of_compare_left_with_right == INT8_C(-1));
        const bool left_is_equal_to_right  = (result_of_compare_left_with_right == INT8_C( 0));

        if(left_is_less_than_right)
        {
          // If the denominator is larger than the numerator,
          // then the result of the division is zero.
          if(remainder != nullptr)
          {
            *remainder = *this;
          }

          operator=(static_cast<std::uint8_t>(0U));
        }
        else if(left_is_equal_to_right)
        {
          // If the denominator is equal to the numerator,
          // then the result of the division is one.
          operator=(static_cast<std::uint8_t>(1U));

          if(remainder != nullptr)
          {
            *remainder = uintwide_t(static_cast<std::uint8_t>(0U));
          }
        }
        else
        {
          eval_divide_knuth_core(u_offset, v_offset, other, remainder);
        }
      }
    }

    template<const size_t RePhraseWidth2 = Width2,
             std::enable_if_t<(RePhraseWidth2 > std::numeric_limits<limb_type>::digits)> const* = nullptr>
    WIDE_INTEGER_CONSTEXPR auto eval_divide_knuth_core(const unsigned_fast_type u_offset, // NOLINT(readability-function-cognitive-complexity)
                                                       const unsigned_fast_type v_offset,
                                                       const uintwide_t& other,
                                                             uintwide_t* remainder) -> void
    {
      using local_uint_index_type = unsigned_fast_type;

      if(static_cast<local_uint_index_type>(v_offset + static_cast<local_uint_index_type>(1U)) == static_cast<local_uint_index_type>(number_of_limbs))
      {
        // The denominator has one single limb.
        // Use a one-dimensional division algorithm.
        const limb_type short_denominator = *other.values.cbegin();

        eval_divide_by_single_limb(short_denominator, u_offset, remainder);
      }
      else
      {
        // We will now use the Knuth long division algorithm.

        // Compute the normalization factor d.
        const auto d =
          static_cast<limb_type>
          (
              static_cast<double_limb_type>(static_cast<double_limb_type>(1U) << static_cast<unsigned>(std::numeric_limits<limb_type>::digits))
            / static_cast<double_limb_type>(static_cast<double_limb_type>(*(other.values.cbegin() + static_cast<size_t>(static_cast<local_uint_index_type>(number_of_limbs - 1U) - v_offset))) + static_cast<limb_type>(1U))
          );

        // Step D1(b), normalize u -> u * d = uu.
        // Step D1(c): normalize v -> v * d = vv.

        using uu_array_type =
          typename std::conditional<std::is_same<AllocatorType, void>::value,
                                    detail::fixed_static_array <limb_type, number_of_limbs + 1U>,
                                    detail::fixed_dynamic_array<limb_type,
                                                                number_of_limbs + 1U,
                                                                typename std::allocator_traits<typename std::conditional<std::is_same<AllocatorType, void>::value,
                                                                                                                          std::allocator<void>,
                                                                                                                          AllocatorType>::type>::template rebind_alloc<limb_type>>>::type;

        uu_array_type       uu;
        representation_type vv;

        if(d > static_cast<limb_type>(1U))
        {
          const auto num_limbs_minus_u_ofs =
            static_cast<size_t>
            (
              static_cast<local_uint_index_type>(number_of_limbs) - u_offset
            );

          *(uu.begin() + num_limbs_minus_u_ofs) =
            eval_multiply_1d
            (
              uu.data(),
              values.data(),
              d,
              static_cast<unsigned_fast_type>(num_limbs_minus_u_ofs)
            );

          static_cast<void>
          (
            eval_multiply_1d
            (
              vv.data(),
              other.values.data(),
              d,
              static_cast<unsigned_fast_type>(number_of_limbs - v_offset)
            )
          );
        }
        else
        {
          std::copy(values.cbegin(), values.cend(), uu.begin());

          *(uu.begin() + static_cast<size_t>(static_cast<local_uint_index_type>(number_of_limbs) - u_offset)) = static_cast<limb_type>(0U);

          vv = other.values;
        }

        // Step D2: Initialize j.
        // Step D7: Loop on j from m to 0.

        const auto n   = static_cast<local_uint_index_type>                                   (number_of_limbs - v_offset);
        const auto m   = static_cast<local_uint_index_type>(static_cast<local_uint_index_type>(number_of_limbs - u_offset) - n);
        const auto vj0 = static_cast<local_uint_index_type>(static_cast<local_uint_index_type>(number_of_limbs - 1U) - v_offset);

        for(auto j = static_cast<local_uint_index_type>(0U); j <= m; ++j) // NOLINT(altera-id-dependent-backward-branch)
        {
          // Step D3 [Calculate q_hat].
          //   if u[j] == v[j0]
          //     set q_hat = b - 1
          //   else
          //     set q_hat = (u[j] * b + u[j + 1]) / v[1]

          const auto uj     = static_cast<local_uint_index_type>(static_cast<local_uint_index_type>(static_cast<local_uint_index_type>(static_cast<local_uint_index_type>(number_of_limbs + 1U) - 1U) - u_offset) - j);
          const auto u_j_j1 = static_cast<double_limb_type>(static_cast<double_limb_type>(static_cast<double_limb_type>(*(uu.cbegin() + static_cast<size_t>(uj))) << static_cast<unsigned>(std::numeric_limits<limb_type>::digits)) + *(uu.cbegin() + static_cast<size_t>(uj - 1U)));

          limb_type q_hat = ((*(uu.cbegin() + static_cast<size_t>(uj)) == *(vv.cbegin() + static_cast<size_t>(vj0)))
            ? (std::numeric_limits<limb_type>::max)()
            : static_cast<limb_type>(u_j_j1 / *(vv.cbegin() + static_cast<size_t>(vj0))));

          // Decrease q_hat if necessary.
          // This means that q_hat must be decreased if the
          // expression [(u[uj] * b + u[uj - 1] - q_hat * v[vj0 - 1]) * b]
          // exceeds the range of uintwide_t.

          for(auto t = static_cast<double_limb_type>(u_j_j1 - static_cast<double_limb_type>(q_hat * static_cast<double_limb_type>(*(vv.cbegin() + static_cast<size_t>(vj0))))); ; --q_hat, t = static_cast<double_limb_type>(t + *(vv.cbegin() + static_cast<size_t>(vj0))))
          {
            if(   (detail::make_hi<limb_type>(t) != static_cast<limb_type>(0U))
                || (   static_cast<double_limb_type>(static_cast<double_limb_type>(*(vv.cbegin() + static_cast<size_t>(vj0 - 1U))) * q_hat)
                    <= static_cast<double_limb_type>(static_cast<double_limb_type>(t << static_cast<unsigned>(std::numeric_limits<limb_type>::digits)) + *(uu.cbegin() + static_cast<size_t>(uj - 2U)))))
            {
              break;
            }
          }

          // Step D4: Multiply and subtract.
          // Replace u[j, ... j + n] by u[j, ... j + n] - q_hat * v[1, ... n].

          // Set nv = q_hat * (v[1, ... n]).
          uu_array_type nv;

          *(nv.begin() + static_cast<size_t>(n)) = eval_multiply_1d(nv.data(), vv.data(), q_hat, n);

          const bool has_borrow =
            eval_subtract_n(uu.data() + static_cast<size_t>(static_cast<local_uint_index_type>(uj - n)),
                            uu.data() + static_cast<size_t>(static_cast<local_uint_index_type>(uj - n)),
                            nv.data(),
                            n + 1U);


          // Step D5: Test the remainder.
          // Set the result value: Set result.m_data[m - j] = q_hat.
          // Use the condition (u[j] < 0), in other words if the borrow
          // is non-zero, then step D6 needs to be carried out.

          if(has_borrow)
          {
            --q_hat;

            // Step D6: Add back.
            // Add v[1, ... n] back to u[j, ... j + n],
            // and decrease the result by 1.

            static_cast<void>(eval_add_n(uu.data() + static_cast<size_t>(static_cast<local_uint_index_type>(uj - n)),
                                         uu.data() + static_cast<size_t>(static_cast<local_uint_index_type>(uj - n)),
                                         vv.data(),
                                         n));
          }

          // Get the result data.
          *(values.begin() + static_cast<size_t>(m - j)) = static_cast<limb_type>(q_hat);
        }

        // Clear the data elements that have not
        // been computed in the division algorithm.
        {
          const auto m_plus_one =
            static_cast<local_uint_index_type>
            (
              static_cast<local_uint_index_type>(m) + 1U
            );

          std::fill(values.begin() + m_plus_one, values.end(), static_cast<limb_type>(0U));
        }

        if(remainder != nullptr)
        {
          if(d == 1U)
          {
            std::copy(uu.cbegin(),
                      uu.cbegin() + static_cast<size_t>(static_cast<local_uint_index_type>(number_of_limbs - v_offset)),
                      remainder->values.begin());
          }
          else
          {
            auto previous_u = static_cast<limb_type>(0U);

            for(auto rl = static_cast<signed_fast_type>(n - 1U), ul = static_cast<signed_fast_type>(number_of_limbs - (v_offset + 1U)); rl >= 0; --rl, --ul) // NOLINT(altera-id-dependent-backward-branch)
            {
              const auto t =
                static_cast<double_limb_type>(  *(uu.cbegin() + static_cast<size_t>(ul))
                                              + static_cast<double_limb_type>(static_cast<double_limb_type>(previous_u) << static_cast<unsigned>(std::numeric_limits<limb_type>::digits)));

              *(remainder->values.begin() + static_cast<size_t>(rl)) = static_cast<limb_type>(static_cast<double_limb_type>(t / d));
              previous_u                                             = static_cast<limb_type>(static_cast<double_limb_type>(t - static_cast<double_limb_type>(static_cast<double_limb_type>(d) * *(remainder->values.cbegin() + static_cast<size_t>(rl)))));
            }
          }

          std::fill(remainder->values.begin() + static_cast<size_t>(n),
                    remainder->values.end(),
                    static_cast<limb_type>(0U));
        }
      }
    }

    template<const size_t RePhraseWidth2 = Width2,
             std::enable_if_t<(RePhraseWidth2 <= std::numeric_limits<limb_type>::digits)> const* = nullptr>
    WIDE_INTEGER_CONSTEXPR auto eval_divide_knuth_core(const unsigned_fast_type u_offset,
                                                       const unsigned_fast_type v_offset,
                                                       const uintwide_t& other,
                                                             uintwide_t* remainder) -> void
    {
      static_cast<void>(v_offset);

      // The denominator has one single limb.
      // Use a one-dimensional division algorithm.
      const limb_type short_denominator = *other.values.cbegin();

      eval_divide_by_single_limb(short_denominator, u_offset, remainder);
    }

    template<typename IntegralType>
    static constexpr auto exceeds_width(IntegralType n) -> bool
    {
      return (static_cast<size_t>(n) >= uintwide_t::my_width2);
    }

    WIDE_INTEGER_NODISCARD constexpr auto right_shift_fill_value() const -> limb_type
    {
      return
        static_cast<limb_type>
        (
          (!is_neg(*this)) ? static_cast<limb_type>(0U)
                           : (std::numeric_limits<limb_type>::max)()
        );
    }

    template<typename IntegralType>
    WIDE_INTEGER_CONSTEXPR auto shl(IntegralType n) -> void
    {
      const auto offset            = static_cast<unsigned_fast_type>(static_cast<unsigned_fast_type>(n) / static_cast<unsigned_fast_type>(std::numeric_limits<limb_type>::digits));
      const auto left_shift_amount = static_cast<std::uint_fast16_t>(static_cast<unsigned_fast_type>(n) % static_cast<unsigned_fast_type>(std::numeric_limits<limb_type>::digits));

      if(offset > 0U)
      {
        std::copy_backward(values.data(),
                           values.data() + static_cast<size_t>(number_of_limbs - offset),
                           values.data() + static_cast<size_t>(number_of_limbs));

        std::fill(values.begin(), values.begin() + static_cast<size_t>(offset), static_cast<limb_type>(0U));
      }

      using local_integral_type = unsigned_fast_type;

      if(left_shift_amount != static_cast<local_integral_type>(0U))
      {
        auto part_from_previous_value = static_cast<limb_type>(0U);

        for(unsigned_fast_type i = offset; i < static_cast<unsigned_fast_type>(number_of_limbs); ++i) // NOLINT(altera-id-dependent-backward-branch)
        {
          const limb_type t = *(values.cbegin() + static_cast<size_t>(i));

          *(values.begin() + static_cast<size_t>(i)) =
            static_cast<limb_type>(static_cast<limb_type>(t << static_cast<local_integral_type>(left_shift_amount)) | part_from_previous_value);

          part_from_previous_value =
            static_cast<limb_type>(t >> static_cast<local_integral_type>(static_cast<unsigned_fast_type>(std::numeric_limits<limb_type>::digits - left_shift_amount)));
        }
      }
    }

    template<typename IntegralType>
    WIDE_INTEGER_CONSTEXPR auto shr(IntegralType n) -> void
    {
      const auto offset             = static_cast<unsigned_fast_type>(static_cast<unsigned_fast_type>(n) / static_cast<unsigned_fast_type>(std::numeric_limits<limb_type>::digits));
      const auto right_shift_amount = static_cast<std::uint_fast16_t>(static_cast<unsigned_fast_type>(n) % static_cast<unsigned_fast_type>(std::numeric_limits<limb_type>::digits));

      if(offset > 0U)
      {
        std::copy(values.begin() + static_cast<size_t>(offset),
                  values.begin() + static_cast<size_t>(number_of_limbs),
                  values.begin());

        std::fill(values.end() - static_cast<size_t>(offset),
                  values.end(),
                  right_shift_fill_value());
      }

      using local_integral_type = unsigned_fast_type;

      if(right_shift_amount != static_cast<local_integral_type>(0U))
      {
        auto part_from_previous_value =
          static_cast<limb_type>
          (
            (!is_neg(*this))
              ? static_cast<limb_type>(0U)
              : static_cast<limb_type>((std::numeric_limits<limb_type>::max)() << static_cast<std::uint_fast16_t>(static_cast<std::uint_fast16_t>(std::numeric_limits<limb_type>::digits) - right_shift_amount))
          );

        for(auto i = static_cast<signed_fast_type>((number_of_limbs - 1U) - offset); i >= static_cast<signed_fast_type>(0); --i) // NOLINT(altera-id-dependent-backward-branch)
        {
          const limb_type t = *(values.cbegin() + static_cast<size_t>(i));

          *(values.begin() + static_cast<size_t>(i)) = static_cast<limb_type>(static_cast<limb_type>(t >> static_cast<local_integral_type>(right_shift_amount)) | part_from_previous_value);

          part_from_previous_value = static_cast<limb_type>(t << static_cast<local_integral_type>(static_cast<unsigned_fast_type>(std::numeric_limits<limb_type>::digits - right_shift_amount)));
        }
      }
    }

    // Read string function.
    WIDE_INTEGER_CONSTEXPR auto rd_string(const char* str_input) -> bool // NOLINT(readability-function-cognitive-complexity)
    {
      std::fill(values.begin(), values.end(), static_cast<limb_type>(0U));

      const auto str_length = detail::strlen_unsafe(str_input);

      auto base = static_cast<std::uint_fast8_t>(UINT8_C(10));

      auto pos = static_cast<unsigned_fast_type>(0U);

      // Detect: Is there a plus sign?
      // And if there is a plus sign, skip over the plus sign.
      if((str_length > 0U) && (str_input[0U] == '+')) // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
      {
        ++pos;
      }

      bool str_has_neg_sign = false;

      // Detect: Is there a minus sign?
      // And if there is a minus sign, skip over the minus sign.
      if((str_length > 0U) && (str_input[0U] == '-')) // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
      {
        str_has_neg_sign = true;

        ++pos;
      }

      // Perform a dynamic detection of the base.
      if(str_length > (pos + 0U))
      {
        const bool might_be_oct_or_hex = ((str_input[pos + 0U] == '0') && (str_length > (pos + 1U))); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)

        if(might_be_oct_or_hex)
        {
          if((str_input[pos + 1U] >= '0') && (str_input[pos + 1U] <= '8')) // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
          {
            // The input format is octal.
            base = UINT8_C(8);

            pos += 1U;
          }
          else if((str_input[pos + 1U] == 'x') || (str_input[pos + 1U] == 'X')) // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
          {
            // The input format is hexadecimal.
            base = UINT8_C(16);

            pos += 2U;
          }
        }
        else if((str_input[pos + 0U] >= '0') && (str_input[pos + 0U] <= '9')) // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
        {
          // The input format is decimal.
          ;
        }
      }

      bool char_is_valid = true;

      while((pos < str_length) && char_is_valid) // NOLINT(altera-id-dependent-backward-branch)
      {
        const auto c = str_input[pos++]; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)

        const auto char_is_apostrophe = (c == static_cast<char>(39));

        if(!char_is_apostrophe)
        {
          if(base == UINT8_C(8))
          {
            char_is_valid = ((c >= '0') && (c <= '8'));

            if(char_is_valid)
            {
              const auto uc_oct = static_cast<std::uint8_t>(c - static_cast<char>(UINT8_C(0x30)));

              operator<<=(3U);

              *values.begin() = static_cast<limb_type>(*values.begin() | uc_oct);
            }
          }
          else if(base == UINT8_C(10))
          {
            char_is_valid = ((c >= '0') && (c <= '9'));

            if(char_is_valid)
            {
              const auto uc_dec = static_cast<std::uint8_t>(c - static_cast<char>(UINT8_C(0x30)));

              mul_by_limb(static_cast<limb_type>(UINT8_C(10)));

              operator+=(uc_dec);
            }
          }
          else if(base == UINT8_C(16))
          {
            const auto char_is_a_to_f_lo((c >= 'a') && (c <= 'f'));
            const auto char_is_a_to_f_hi((c >= 'A') && (c <= 'F'));
            const auto char_is_0_to_9   ((c >= '0') && (c <= '9'));

            char_is_valid = (char_is_a_to_f_lo || char_is_a_to_f_hi || char_is_0_to_9);

            if(char_is_valid)
            {
              auto uc_hex = static_cast<std::uint8_t>(0U);

              if     (char_is_a_to_f_lo) { uc_hex = static_cast<std::uint8_t>(c - static_cast<char>(UINT8_C(  87))); }
              else if(char_is_a_to_f_hi) { uc_hex = static_cast<std::uint8_t>(c - static_cast<char>(UINT8_C(  55))); }
              else if(char_is_0_to_9)    { uc_hex = static_cast<std::uint8_t>(c - static_cast<char>(UINT8_C(0x30))); }

              operator<<=(4U);

              *values.begin() = static_cast<limb_type>(*values.begin() | uc_hex);
            }
          }
        }
      }

      if(str_has_neg_sign)
      {
        negate();
      }

      return char_is_valid;
    }

    WIDE_INTEGER_CONSTEXPR auto bitwise_not() -> void
    {
      for(auto i = static_cast<unsigned_fast_type>(0U); i < number_of_limbs; ++i)
      {
        *(values.begin() + static_cast<size_t>(i)) = static_cast<limb_type>(~(*(values.cbegin() + static_cast<size_t>(i))));
      }
    }

    WIDE_INTEGER_CONSTEXPR auto preincrement() -> void
    {
      // Implement self-increment.

      auto it = values.begin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto)

      do
      {
        ++(*it);
      }
      while((*it++ == static_cast<limb_type>(0U)) && (it != values.end())); // NOLINT(altera-id-dependent-backward-branch)
    }

    WIDE_INTEGER_CONSTEXPR auto predecrement() -> void
    {
      // Implement self-decrement.

      auto it = values.begin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto)

      do
      {
        --(*it);
      }
      while((*it++ == (std::numeric_limits<limb_type>::max)()) && (it != values.end())); // NOLINT(altera-id-dependent-backward-branch)
    }
  };

  // Define some convenient unsigned wide integer types.
  using uint64_t    = uintwide_t<static_cast<size_t>(UINT32_C(   64)), std::uint16_t>;
  using uint128_t   = uintwide_t<static_cast<size_t>(UINT32_C(  128)), std::uint32_t>;
  using uint256_t   = uintwide_t<static_cast<size_t>(UINT32_C(  256)), std::uint32_t>;
  using uint512_t   = uintwide_t<static_cast<size_t>(UINT32_C(  512)), std::uint32_t>;
  using uint1024_t  = uintwide_t<static_cast<size_t>(UINT32_C( 1024)), std::uint32_t>;
  using uint2048_t  = uintwide_t<static_cast<size_t>(UINT32_C( 2048)), std::uint32_t>;
  using uint4096_t  = uintwide_t<static_cast<size_t>(UINT32_C( 4096)), std::uint32_t>;
  using uint8192_t  = uintwide_t<static_cast<size_t>(UINT32_C( 8192)), std::uint32_t>;
  using uint16384_t = uintwide_t<static_cast<size_t>(UINT32_C(16384)), std::uint32_t>;
  using uint32768_t = uintwide_t<static_cast<size_t>(UINT32_C(32768)), std::uint32_t>;
  using uint65536_t = uintwide_t<static_cast<size_t>(UINT32_C(65536)), std::uint32_t>;

  #if !defined(WIDE_INTEGER_DISABLE_TRIVIAL_COPY_AND_STD_LAYOUT_CHECKS)
  static_assert(std::is_trivially_copyable<uint64_t   >::value, "uintwide_t must be trivially copyable.");
  static_assert(std::is_trivially_copyable<uint128_t  >::value, "uintwide_t must be trivially copyable.");
  static_assert(std::is_trivially_copyable<uint256_t  >::value, "uintwide_t must be trivially copyable.");
  static_assert(std::is_trivially_copyable<uint512_t  >::value, "uintwide_t must be trivially copyable.");
  static_assert(std::is_trivially_copyable<uint1024_t >::value, "uintwide_t must be trivially copyable.");
  static_assert(std::is_trivially_copyable<uint2048_t >::value, "uintwide_t must be trivially copyable.");
  static_assert(std::is_trivially_copyable<uint4096_t >::value, "uintwide_t must be trivially copyable.");
  static_assert(std::is_trivially_copyable<uint8192_t >::value, "uintwide_t must be trivially copyable.");
  static_assert(std::is_trivially_copyable<uint16384_t>::value, "uintwide_t must be trivially copyable.");
  static_assert(std::is_trivially_copyable<uint32768_t>::value, "uintwide_t must be trivially copyable.");
  static_assert(std::is_trivially_copyable<uint65536_t>::value, "uintwide_t must be trivially copyable.");

  static_assert(std::is_standard_layout<uint64_t   >::value, "uintwide_t must have standard layout.");
  static_assert(std::is_standard_layout<uint128_t  >::value, "uintwide_t must have standard layout.");
  static_assert(std::is_standard_layout<uint256_t  >::value, "uintwide_t must have standard layout.");
  static_assert(std::is_standard_layout<uint512_t  >::value, "uintwide_t must have standard layout.");
  static_assert(std::is_standard_layout<uint1024_t >::value, "uintwide_t must have standard layout.");
  static_assert(std::is_standard_layout<uint2048_t >::value, "uintwide_t must have standard layout.");
  static_assert(std::is_standard_layout<uint4096_t >::value, "uintwide_t must have standard layout.");
  static_assert(std::is_standard_layout<uint8192_t >::value, "uintwide_t must have standard layout.");
  static_assert(std::is_standard_layout<uint16384_t>::value, "uintwide_t must have standard layout.");
  static_assert(std::is_standard_layout<uint32768_t>::value, "uintwide_t must have standard layout.");
  static_assert(std::is_standard_layout<uint65536_t>::value, "uintwide_t must have standard layout.");
  #endif

  using  int64_t    = uintwide_t<static_cast<size_t>(UINT32_C(   64)), std::uint16_t, void, true>;
  using  int128_t   = uintwide_t<static_cast<size_t>(UINT32_C(  128)), std::uint32_t, void, true>;
  using  int256_t   = uintwide_t<static_cast<size_t>(UINT32_C(  256)), std::uint32_t, void, true>;
  using  int512_t   = uintwide_t<static_cast<size_t>(UINT32_C(  512)), std::uint32_t, void, true>;
  using  int1024_t  = uintwide_t<static_cast<size_t>(UINT32_C( 1024)), std::uint32_t, void, true>;
  using  int2048_t  = uintwide_t<static_cast<size_t>(UINT32_C( 2048)), std::uint32_t, void, true>;
  using  int4096_t  = uintwide_t<static_cast<size_t>(UINT32_C( 4096)), std::uint32_t, void, true>;
  using  int8192_t  = uintwide_t<static_cast<size_t>(UINT32_C( 8192)), std::uint32_t, void, true>;
  using  int16384_t = uintwide_t<static_cast<size_t>(UINT32_C(16384)), std::uint32_t, void, true>;
  using  int32768_t = uintwide_t<static_cast<size_t>(UINT32_C(32768)), std::uint32_t, void, true>;
  using  int65536_t = uintwide_t<static_cast<size_t>(UINT32_C(65536)), std::uint32_t, void, true>;

  #if !defined(WIDE_INTEGER_DISABLE_TRIVIAL_COPY_AND_STD_LAYOUT_CHECKS)
  static_assert(std::is_trivially_copyable<int64_t   >::value, "uintwide_t must be trivially copyable.");
  static_assert(std::is_trivially_copyable<int128_t  >::value, "uintwide_t must be trivially copyable.");
  static_assert(std::is_trivially_copyable<int256_t  >::value, "uintwide_t must be trivially copyable.");
  static_assert(std::is_trivially_copyable<int512_t  >::value, "uintwide_t must be trivially copyable.");
  static_assert(std::is_trivially_copyable<int1024_t >::value, "uintwide_t must be trivially copyable.");
  static_assert(std::is_trivially_copyable<int2048_t >::value, "uintwide_t must be trivially copyable.");
  static_assert(std::is_trivially_copyable<int4096_t >::value, "uintwide_t must be trivially copyable.");
  static_assert(std::is_trivially_copyable<int8192_t >::value, "uintwide_t must be trivially copyable.");
  static_assert(std::is_trivially_copyable<int16384_t>::value, "uintwide_t must be trivially copyable.");
  static_assert(std::is_trivially_copyable<int32768_t>::value, "uintwide_t must be trivially copyable.");
  static_assert(std::is_trivially_copyable<int65536_t>::value, "uintwide_t must be trivially copyable.");

  static_assert(std::is_standard_layout<int64_t   >::value, "uintwide_t must have standard layout.");
  static_assert(std::is_standard_layout<int128_t  >::value, "uintwide_t must have standard layout.");
  static_assert(std::is_standard_layout<int256_t  >::value, "uintwide_t must have standard layout.");
  static_assert(std::is_standard_layout<int512_t  >::value, "uintwide_t must have standard layout.");
  static_assert(std::is_standard_layout<int1024_t >::value, "uintwide_t must have standard layout.");
  static_assert(std::is_standard_layout<int2048_t >::value, "uintwide_t must have standard layout.");
  static_assert(std::is_standard_layout<int4096_t >::value, "uintwide_t must have standard layout.");
  static_assert(std::is_standard_layout<int8192_t >::value, "uintwide_t must have standard layout.");
  static_assert(std::is_standard_layout<int16384_t>::value, "uintwide_t must have standard layout.");
  static_assert(std::is_standard_layout<int32768_t>::value, "uintwide_t must have standard layout.");
  static_assert(std::is_standard_layout<int65536_t>::value, "uintwide_t must have standard layout.");
  #endif

  // Insert a base class for numeric_limits<> support.
  // This class inherits from std::numeric_limits<unsigned int>
  // in order to provide limits for a non-specific unsigned type.

  template<const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  WIDE_INTEGER_NUM_LIMITS_CLASS_TYPE numeric_limits_uintwide_t_base
    : public std::numeric_limits<typename std::conditional<(!IsSigned), unsigned int, signed int>::type>
  {
  private:
    using local_wide_integer_type = uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;

  public:
    static constexpr int digits          = (!IsSigned)
                                             ? static_cast<int>(local_wide_integer_type::my_width2)
                                             : static_cast<int>(local_wide_integer_type::my_width2 - 1U);

    static constexpr int digits10        = static_cast<int>((static_cast<std::uintmax_t>(digits)       * UINTMAX_C(75257499)) / UINTMAX_C(250000000));
    static constexpr int max_digits10    = digits10;
    static constexpr int max_exponent    = digits;
    static constexpr int max_exponent10  = static_cast<int>((static_cast<std::uintmax_t>(max_exponent) * UINTMAX_C(75257499)) / UINTMAX_C(250000000));

    static constexpr auto (max) () -> local_wide_integer_type { return local_wide_integer_type::limits_helper_max   (IsSigned); }
    static constexpr auto (min) () -> local_wide_integer_type { return local_wide_integer_type::limits_helper_min   (IsSigned); }
    static constexpr auto lowest() -> local_wide_integer_type { return local_wide_integer_type::limits_helper_lowest(IsSigned); }
  };

  template<class T>
  struct is_integral : public std::is_integral<T> { };

  template<const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  struct is_integral<math::wide_integer::uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>
    : public std::integral_constant<bool, true> { };

  #if(__cplusplus >= 201703L)
  } // namespace math::wide_integer
  #else
  } // namespace wide_integer
  } // namespace math
  #endif

  WIDE_INTEGER_NAMESPACE_END

  namespace std
  {
    // Specialization of std::numeric_limits<uintwide_t>.
    #if defined(WIDE_INTEGER_NAMESPACE)
    template<const WIDE_INTEGER_NAMESPACE::math::wide_integer::size_t Width2,
             typename LimbType,
             typename AllocatorType,
             const bool IsSigned>
    WIDE_INTEGER_NUM_LIMITS_CLASS_TYPE numeric_limits<WIDE_INTEGER_NAMESPACE::math::wide_integer::uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>
      : public WIDE_INTEGER_NAMESPACE::math::wide_integer::numeric_limits_uintwide_t_base<Width2, LimbType, AllocatorType, IsSigned> { };
    #else
    template<const ::math::wide_integer::size_t Width2,
             typename LimbType,
             typename AllocatorType,
             const bool IsSigned>
    WIDE_INTEGER_NUM_LIMITS_CLASS_TYPE numeric_limits<::math::wide_integer::uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>
      : public ::math::wide_integer::numeric_limits_uintwide_t_base<Width2, LimbType, AllocatorType, IsSigned> { };
    #endif
  } // namespace std

  WIDE_INTEGER_NAMESPACE_BEGIN

  #if(__cplusplus >= 201703L)
  namespace math::wide_integer {
  #else
  namespace math { namespace wide_integer { // NOLINT(modernize-concat-nested-namespaces)
  #endif

  // Non-member binary add, sub, mul, div, mod of (uintwide_t op uintwide_t).
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator+ (const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator+=(v); }
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator- (const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator-=(v); }
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator* (const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator*=(v); }
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator/ (const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator/=(v); }
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator% (const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator%=(v); }

  // Non-member binary logic operations of (uintwide_t op uintwide_t).
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator| (const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator|=(v); }
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator^ (const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator^=(v); }
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator& (const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator&=(v); }

  // Non-member binary add, sub, mul, div, mod of (uintwide_t op IntegralType).
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator+(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator+=(uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(v)); }
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator-(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator-=(uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(v)); }
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator*(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator*=(uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(v)); }
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator/(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator/=(uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(v)); }

  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned>
  constexpr auto operator%(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<(   std::is_integral<IntegralType>::value
                                                                                                                                       && std::is_signed<IntegralType>::value),
                                                                                                                                          uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>
  {
    using local_wide_integer_type = uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;

    return local_wide_integer_type(u).operator%=(local_wide_integer_type(v));
  }

  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned>
  WIDE_INTEGER_CONSTEXPR auto operator%(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<(    std::is_integral<IntegralType>::value
                                                                                                                                                    &&  std::is_unsigned<IntegralType>::value
                                                                                                                                                    && (std::numeric_limits<IntegralType>::digits <= std::numeric_limits<LimbType>::digits)),
                                                                                                                                                    typename uintwide_t<Width2, LimbType, AllocatorType, IsSigned>::limb_type>
  {
    using local_wide_integer_type = uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;

    const bool u_is_neg = local_wide_integer_type::is_neg(u);

    local_wide_integer_type remainder;

    local_wide_integer_type((!u_is_neg) ? u : -u).eval_divide_by_single_limb(v, 0U, &remainder);

    using local_limb_type = typename local_wide_integer_type::limb_type;

    auto u_rem = static_cast<local_limb_type>(remainder);

    return ((!u_is_neg) ? u_rem : static_cast<local_limb_type>(static_cast<local_limb_type>(~u_rem) + 1U));
  }

  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned>
  constexpr auto operator%(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<(    std::is_integral<IntegralType>::value
                                                                                                                                       &&  std::is_unsigned<IntegralType>::value
                                                                                                                                       && (std::numeric_limits<IntegralType>::digits > std::numeric_limits<LimbType>::digits)),
                                                                                                                                       uintwide_t<Width2, LimbType, AllocatorType, IsSigned>>
  {
    using local_wide_integer_type = uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;

    return local_wide_integer_type(u).operator%=(local_wide_integer_type(v));
  }

  // Non-member binary add, sub, mul, div, mod of (IntegralType op uintwide_t).
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator+(const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator+=(v); }
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator-(const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator-=(v); }
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator*(const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator*=(v); }
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator/(const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator/=(v); }
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator%(const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator%=(v); }

  #if !defined(WIDE_INTEGER_DISABLE_FLOAT_INTEROP)
  // Non-member binary add, sub, mul, div, mod of (uintwide_t op FloatingPointType).
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator+(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const FloatingPointType& f) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator+=(uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(f)); }
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator-(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const FloatingPointType& f) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator-=(uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(f)); }
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator*(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const FloatingPointType& f) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator*=(uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(f)); }
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator/(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const FloatingPointType& f) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator/=(uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(f)); }
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator%(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const FloatingPointType& f) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator%=(uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(f)); }

  // Non-member binary add, sub, mul, div, mod of (FloatingPointType op uintwide_t).
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator+(const FloatingPointType& f, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(f).operator+=(v); }
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator-(const FloatingPointType& f, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(f).operator-=(v); }
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator*(const FloatingPointType& f, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(f).operator*=(v); }
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator/(const FloatingPointType& f, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(f).operator/=(v); }
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator%(const FloatingPointType& f, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(f).operator%=(v); }
  #endif

  // Non-member binary logic operations of (uintwide_t op IntegralType).
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator|(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator|=(uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(v)); }
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator^(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator^=(uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(v)); }
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator&(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator&=(uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(v)); }

  // Non-member binary binary logic operations of (IntegralType op uintwide_t).
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator|(const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator|=(v); }
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator^(const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator^=(v); }
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator&(const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator&=(v); }

  // Non-member shift functions of (uintwide_t shift IntegralType).
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator<<(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType n) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator<<=(n); }
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator>>(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType n) -> std::enable_if_t<std::is_integral<IntegralType>::value, uintwide_t<Width2, LimbType, AllocatorType, IsSigned>> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator>>=(n); }

  // Non-member comparison functions of (uintwide_t cmp uintwide_t).
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator==(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> bool { return u.operator==(v); }
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator!=(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> bool { return u.operator!=(v); }
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator> (const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> bool { return u.operator> (v); }
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator< (const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> bool { return u.operator< (v); }
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator>=(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> bool { return u.operator>=(v); }
  template<const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator<=(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> bool { return u.operator<=(v); }

  // Non-member comparison functions of (uintwide_t cmp IntegralType).
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator==(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, bool> { return u.operator==(uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(v)); }
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator!=(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, bool> { return u.operator!=(uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(v)); }
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator> (const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, bool> { return u.operator> (uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(v)); }
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator< (const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, bool> { return u.operator< (uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(v)); }
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator>=(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, bool> { return u.operator>=(uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(v)); }
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator<=(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const IntegralType& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, bool> { return u.operator<=(uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(v)); }

  // Non-member comparison functions of (IntegralType cmp uintwide_t).
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator==(const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, bool> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator==(v); }
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator!=(const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, bool> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator!=(v); }
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator> (const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, bool> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator> (v); }
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator< (const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, bool> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator< (v); }
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator>=(const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, bool> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator>=(v); }
  template<typename IntegralType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator<=(const IntegralType& u, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_integral<IntegralType>::value, bool> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(u).operator<=(v); }

  #if !defined(WIDE_INTEGER_DISABLE_FLOAT_INTEROP)
  // Non-member comparison functions of (uintwide_t cmp FloatingPointType).
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator==(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const FloatingPointType& f) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, bool> { return u.operator==(uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(f)); }
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator!=(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const FloatingPointType& f) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, bool> { return u.operator!=(uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(f)); }
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator> (const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const FloatingPointType& f) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, bool> { return u.operator> (uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(f)); }
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator< (const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const FloatingPointType& f) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, bool> { return u.operator< (uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(f)); }
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator>=(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const FloatingPointType& f) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, bool> { return u.operator>=(uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(f)); }
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator<=(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& u, const FloatingPointType& f) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, bool> { return u.operator<=(uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(f)); }

  // Non-member comparison functions of (FloatingPointType cmp uintwide_t).
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator==(const FloatingPointType& f, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, bool> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(f).operator==(v); }
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator!=(const FloatingPointType& f, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, bool> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(f).operator!=(v); }
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator> (const FloatingPointType& f, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, bool> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(f).operator> (v); }
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator< (const FloatingPointType& f, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, bool> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(f).operator< (v); }
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator>=(const FloatingPointType& f, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, bool> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(f).operator>=(v); }
  template<typename FloatingPointType, const size_t Width2, typename LimbType, typename AllocatorType, const bool IsSigned> constexpr auto operator<=(const FloatingPointType& f, const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& v) -> std::enable_if_t<std::is_floating_point<FloatingPointType>::value, bool> { return uintwide_t<Width2, LimbType, AllocatorType, IsSigned>(f).operator<=(v); }
  #endif // !defined(WIDE_INTEGER_DISABLE_FLOAT_INTEROP)

  #if !defined(WIDE_INTEGER_DISABLE_IOSTREAM)

  // I/O streaming functions.
  template<typename char_type,
           typename traits_type,
           const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  auto operator<<(std::basic_ostream<char_type, traits_type>& out,
                  const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& x) -> std::basic_ostream<char_type, traits_type>&
  {
    std::basic_ostringstream<char_type, traits_type> ostr;

    const std::ios::fmtflags my_flags = out.flags();

    const bool show_pos     = ((my_flags & std::ios::showpos)   == std::ios::showpos);
    const bool show_base    = ((my_flags & std::ios::showbase)  == std::ios::showbase);
    const bool is_uppercase = ((my_flags & std::ios::uppercase) == std::ios::uppercase);

    std::uint_fast8_t base_rep { };

    if     ((my_flags & std::ios::oct) == std::ios::oct) { base_rep = UINT8_C( 8); }
    else if((my_flags & std::ios::hex) == std::ios::hex) { base_rep = UINT8_C(16); }
    else                                                 { base_rep = UINT8_C(10); }

    const auto field_width = static_cast<unsigned_fast_type>(out.width());
    const auto fill_char   = static_cast<char>(out.fill());

    using local_wide_integer_type = uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;

    if(base_rep == UINT8_C(8))
    {
      using string_storage_oct_type =
        typename std::conditional
          <local_wide_integer_type::my_width2 <= static_cast<size_t>(UINT32_C(2048)),
            detail::fixed_static_array <char,
                                        local_wide_integer_type::wr_string_max_buffer_size_oct>,
            detail::fixed_dynamic_array<char,
                                        local_wide_integer_type::wr_string_max_buffer_size_oct,
                                        typename std::allocator_traits<typename std::conditional<std::is_same<AllocatorType, void>::value,
                                                                                                std::allocator<void>,
                                                                                                AllocatorType>::type>::template rebind_alloc<typename local_wide_integer_type::limb_type>>
          >::type;

      // TBD: There is redundant storage of this kind both here
      // in this subroutine as well as in the wr_string method.
      string_storage_oct_type str_result;

      str_result.fill('\0');

      x.wr_string(str_result.data(), base_rep, show_base, show_pos, is_uppercase, field_width, fill_char);

      static_cast<void>(ostr << str_result.data());
    }
    else if(base_rep == UINT8_C(10))
    {
      using string_storage_dec_type =
        typename std::conditional
          <local_wide_integer_type::my_width2 <= static_cast<size_t>(UINT32_C(2048)),
            detail::fixed_static_array <char,
                                        local_wide_integer_type::wr_string_max_buffer_size_dec>,
            detail::fixed_dynamic_array<char,
                                        local_wide_integer_type::wr_string_max_buffer_size_dec,
                                        typename std::allocator_traits<typename std::conditional<std::is_same<AllocatorType, void>::value,
                                                                                                std::allocator<void>,
                                                                                                AllocatorType>::type>::template rebind_alloc<typename local_wide_integer_type::limb_type>>
          >::type;

      // TBD: There is redundant storage of this kind both here
      // in this subroutine as well as in the wr_string method.
      string_storage_dec_type str_result;

      str_result.fill('\0');

      x.wr_string(str_result.data(), base_rep, show_base, show_pos, is_uppercase, field_width, fill_char);

      static_cast<void>(ostr << str_result.data());
    }
    else if(base_rep == UINT8_C(16))
    {
      using string_storage_hex_type =
        typename std::conditional
          <local_wide_integer_type::my_width2 <= static_cast<size_t>(UINT32_C(2048)),
            detail::fixed_static_array <char,
                                        local_wide_integer_type::wr_string_max_buffer_size_hex>,
            detail::fixed_dynamic_array<char,
                                        local_wide_integer_type::wr_string_max_buffer_size_hex,
                                        typename std::allocator_traits<typename std::conditional<std::is_same<AllocatorType, void>::value,
                                                                                                std::allocator<void>,
                                                                                                AllocatorType>::type>::template rebind_alloc<typename local_wide_integer_type::limb_type>>
          >::type;

      // TBD: There is redundant storage of this kind both here
      // in this subroutine as well as in the wr_string method.
      string_storage_hex_type str_result;

      str_result.fill('\0');

      x.wr_string(str_result.data(), base_rep, show_base, show_pos, is_uppercase, field_width, fill_char);

      static_cast<void>(ostr << str_result.data());
    }

    return (out << ostr.str());
  }

  template<typename char_type,
           typename traits_type,
           const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  auto operator>>(std::basic_istream<char_type, traits_type>& in,
                  uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& x) -> std::basic_istream<char_type, traits_type>&
  {
    std::string str_in;

    in >> str_in;

    using local_wide_integer_type = uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;

    x = local_wide_integer_type(str_in.c_str());

    return in;
  }

  #endif // !defined(WIDE_INTEGER_DISABLE_IOSTREAM)

  #if(__cplusplus >= 201703L)
  } // namespace math::wide_integer
  #else
  } // namespace wide_integer
  } // namespace math
  #endif

  // Implement various number-theoretical tools.

  #if(__cplusplus >= 201703L)
  namespace math::wide_integer {
  #else
  namespace math { namespace wide_integer { // NOLINT(modernize-concat-nested-namespaces)
  #endif

  namespace detail {

  #if !defined(WIDE_INTEGER_DISABLE_FLOAT_INTEROP)
  namespace my_own {

  template<typename FloatingPointType> WIDE_INTEGER_CONSTEXPR auto frexp(FloatingPointType x, int* expptr) -> std::enable_if_t<(std::is_floating_point<FloatingPointType>::value &&   std::numeric_limits<FloatingPointType>::is_iec559 ), FloatingPointType>
  {
    using local_floating_point_type = FloatingPointType;

    const bool x_is_neg = (x < static_cast<local_floating_point_type>(0.0L));

    local_floating_point_type f = (x_is_neg ? -x : x);

    int e2 = 0;

    constexpr long double two_pow32 =
      static_cast<long double>(0x10000) * static_cast<long double>(0x10000);

    while(f >= static_cast<local_floating_point_type>(two_pow32)) // NOLINT(altera-id-dependent-backward-branch)
    {
      // TBD: Maybe optimize this exponent reduction
      // with a more clever kind of binary searching.

      f   = static_cast<local_floating_point_type>(f / static_cast<local_floating_point_type>(two_pow32));
      e2 += static_cast<int>(INT32_C(32));
    }

    constexpr long double one_ldbl(1.0L);

    while(f >= static_cast<local_floating_point_type>(one_ldbl)) // NOLINT(altera-id-dependent-backward-branch)
    {
      constexpr long double two_ldbl(2.0L);

      f = static_cast<local_floating_point_type>(f / static_cast<local_floating_point_type>(two_ldbl));

      ++e2;
    }

    if(expptr != nullptr)
    {
      *expptr = e2;
    }

    return ((!x_is_neg) ? f : -f);
  }

  template<typename FloatingPointType> WIDE_INTEGER_CONSTEXPR auto frexp(FloatingPointType x, int* expptr) -> std::enable_if_t<(std::is_floating_point<FloatingPointType>::value && (!std::numeric_limits<FloatingPointType>::is_iec559)), FloatingPointType>
  {
    using std::frexp;

    return frexp(x, expptr);
  }

  template<typename FloatingPointType> WIDE_INTEGER_CONSTEXPR auto (isfinite)(FloatingPointType x) -> std::enable_if_t<(std::is_floating_point<FloatingPointType>::value &&   std::numeric_limits<FloatingPointType>::is_iec559 ), bool>
  {
    using local_floating_point_type = FloatingPointType;

    bool x_is_finite = true;

    const bool x_is_nan = (x != x);

    if(x_is_nan)
    {
      x_is_finite = false;
    }
    else
    {
      const bool x_is_inf_pos = (x > (std::numeric_limits<local_floating_point_type>::max)());
      const bool x_is_inf_neg = (x <  std::numeric_limits<local_floating_point_type>::lowest());

      if(x_is_inf_pos || x_is_inf_neg)
      {
        x_is_finite = false;
      }
    }

    return x_is_finite;
  }

  template<typename FloatingPointType> WIDE_INTEGER_CONSTEXPR auto (isfinite)(FloatingPointType x) -> std::enable_if_t<(std::is_floating_point<FloatingPointType>::value && (!std::numeric_limits<FloatingPointType>::is_iec559)), bool>
  {
    using std::isfinite;

    return (isfinite)(x);
  }
  } // namespace my_own
  #endif // !defined(WIDE_INTEGER_DISABLE_FLOAT_INTEROP)

  template<typename UnsignedIntegralType>
  inline WIDE_INTEGER_CONSTEXPR auto lsb_helper(const UnsignedIntegralType& u) -> unsigned_fast_type
  {
    // Compile-time checks.
    static_assert((   std::is_integral<UnsignedIntegralType>::value
                   && std::is_unsigned<UnsignedIntegralType>::value),
                   "Error: Please check the characteristics of UnsignedIntegralType");

    unsigned_fast_type result = 0U;

    UnsignedIntegralType mask(u);

    // This assumes that at least one bit is set.
    // Otherwise saturation of the index will occur.

    // Naive and basic LSB search.
    // TBD: This could be improved with a binary search
    // on the lowest bit position of the fundamental type.
    while(static_cast<std::uint_fast8_t>(static_cast<std::uint_fast8_t>(mask) & UINT8_C(1)) == UINT8_C(0)) // NOLINT(hicpp-signed-bitwise,altera-id-dependent-backward-branch)
    {
      mask >>= 1U;

      ++result;
    }

    return result;
  }

  template<typename UnsignedIntegralType>
  inline WIDE_INTEGER_CONSTEXPR auto msb_helper(const UnsignedIntegralType& u) -> unsigned_fast_type
  {
    // Compile-time checks.
    static_assert((   std::is_integral<UnsignedIntegralType>::value
                   && std::is_unsigned<UnsignedIntegralType>::value),
                   "Error: Please check the characteristics of UnsignedIntegralType");

    using local_unsigned_integral_type = UnsignedIntegralType;

    signed_fast_type i { };

    // TBD: This could potentially be improved with a binary
    // search for the highest bit position in the type.

    for(i = static_cast<signed_fast_type>(std::numeric_limits<local_unsigned_integral_type>::digits - 1); i >= 0; --i)
    {
      if((u & static_cast<local_unsigned_integral_type>(static_cast<local_unsigned_integral_type>(1U) << i)) != 0U)
      {
        break;
      }
    }

    return static_cast<unsigned_fast_type>((std::max)(static_cast<signed_fast_type>(0), i));
  }

  template<>
  inline WIDE_INTEGER_CONSTEXPR auto msb_helper<std::uint32_t>(const std::uint32_t& u) -> unsigned_fast_type
  {
    auto r = static_cast<unsigned_fast_type>(0U);
    auto x = static_cast<std::uint_fast32_t>(u);

    // Use O(log2[N]) binary-halving in an unrolled loop to find the msb.
    if((x & UINT32_C(0xFFFF0000)) != UINT32_C(0)) { x = static_cast<std::uint_fast32_t>(x >> static_cast<unsigned>(UINT8_C(16))); r = static_cast<unsigned_fast_type>(r | UINT32_C(16)); }
    if((x & UINT32_C(0x0000FF00)) != UINT32_C(0)) { x = static_cast<std::uint_fast32_t>(x >> static_cast<unsigned>(UINT8_C( 8))); r = static_cast<unsigned_fast_type>(r | UINT32_C( 8)); }
    if((x & UINT32_C(0x000000F0)) != UINT32_C(0)) { x = static_cast<std::uint_fast32_t>(x >> static_cast<unsigned>(UINT8_C( 4))); r = static_cast<unsigned_fast_type>(r | UINT32_C( 4)); }
    if((x & UINT32_C(0x0000000C)) != UINT32_C(0)) { x = static_cast<std::uint_fast32_t>(x >> static_cast<unsigned>(UINT8_C( 2))); r = static_cast<unsigned_fast_type>(r | UINT32_C( 2)); }
    if((x & UINT32_C(0x00000002)) != UINT32_C(0)) {                                                                               r = static_cast<unsigned_fast_type>(r | UINT32_C( 1)); }

    return r;
  }

  template<>
  inline WIDE_INTEGER_CONSTEXPR auto msb_helper<std::uint16_t>(const std::uint16_t& u) -> unsigned_fast_type
  {
    auto r = static_cast<unsigned_fast_type>(0U);
    auto x = static_cast<std::uint_fast16_t>(u);

    // Use O(log2[N]) binary-halving in an unrolled loop to find the msb.
    if(static_cast<std::uint_fast16_t>(static_cast<std::uint_fast32_t>(x) & UINT32_C(0xFF00)) != UINT16_C(0)) { x = static_cast<std::uint_fast16_t>(x >> static_cast<unsigned>(UINT8_C(8))); r = static_cast<unsigned_fast_type>(r | UINT32_C(8)); }
    if(static_cast<std::uint_fast16_t>(static_cast<std::uint_fast32_t>(x) & UINT32_C(0x00F0)) != UINT16_C(0)) { x = static_cast<std::uint_fast16_t>(x >> static_cast<unsigned>(UINT8_C(4))); r = static_cast<unsigned_fast_type>(r | UINT32_C(4)); }
    if(static_cast<std::uint_fast16_t>(static_cast<std::uint_fast32_t>(x) & UINT32_C(0x000C)) != UINT16_C(0)) { x = static_cast<std::uint_fast16_t>(x >> static_cast<unsigned>(UINT8_C(2))); r = static_cast<unsigned_fast_type>(r | UINT32_C(2)); }
    if(static_cast<std::uint_fast16_t>(static_cast<std::uint_fast32_t>(x) & UINT32_C(0x0002)) != UINT16_C(0)) {                                                                              r = static_cast<unsigned_fast_type>(r | UINT32_C(1)); }

    return r;
  }

  template<>
  inline WIDE_INTEGER_CONSTEXPR auto msb_helper<std::uint8_t>(const std::uint8_t& u) -> unsigned_fast_type
  {
    auto r = static_cast<unsigned_fast_type>(0U);
    auto x = static_cast<std::uint_fast8_t>(u);

    // Use O(log2[N]) binary-halving in an unrolled loop to find the msb.
    if(static_cast<std::uint_fast8_t>(static_cast<std::uint_fast32_t>(x) & UINT32_C(0xF0)) != UINT8_C(0)) { x = static_cast<std::uint_fast8_t>(x >> static_cast<unsigned>(UINT8_C(4))); r = static_cast<unsigned_fast_type>(r | UINT32_C(4)); }
    if(static_cast<std::uint_fast8_t>(static_cast<std::uint_fast32_t>(x) & UINT32_C(0x0C)) != UINT8_C(0)) { x = static_cast<std::uint_fast8_t>(x >> static_cast<unsigned>(UINT8_C(2))); r = static_cast<unsigned_fast_type>(r | UINT32_C(2)); }
    if(static_cast<std::uint_fast8_t>(static_cast<std::uint_fast32_t>(x) & UINT32_C(0x02)) != UINT8_C(0)) {                                                                             r = static_cast<unsigned_fast_type>(r | UINT32_C(1)); }

    return r;
  }

  } // namespace detail

  template<const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  WIDE_INTEGER_CONSTEXPR auto swap(uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& x,
                                   uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& y) noexcept -> void
  {
    if(&x != &y)
    {
      std::swap(x, y);
    }
  }

  template<const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  inline WIDE_INTEGER_CONSTEXPR auto lsb(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& x) -> unsigned_fast_type
  {
    // Calculate the position of the least-significant bit.
    // Use a linear search starting from the least significant limbs.

    using local_wide_integer_type = uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;
    using local_value_type        = typename local_wide_integer_type::representation_type::value_type;

    auto bpos   = static_cast<unsigned_fast_type>(0U);
    auto offset = static_cast<unsigned_fast_type>(0U);

    for(auto it = x.crepresentation().cbegin(); it != x.crepresentation().cend(); ++it, ++offset) // NOLINT(llvm-qualified-auto,readability-qualified-auto,altera-id-dependent-backward-branch)
    {
      const auto vi = static_cast<local_value_type>(*it & (std::numeric_limits<local_value_type>::max)());

      if(vi != static_cast<local_value_type>(0U))
      {
        bpos = static_cast<unsigned_fast_type>
               (
                   detail::lsb_helper(*it)
                 + static_cast<unsigned_fast_type>(static_cast<unsigned_fast_type>(std::numeric_limits<local_value_type>::digits) * offset)
               );

        break;
      }
    }

    return bpos;
  }

  template<const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  WIDE_INTEGER_CONSTEXPR auto msb(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& x) -> unsigned_fast_type
  {
    // Calculate the position of the most-significant bit.
    // Use a linear search starting from the most significant limbs.

    using local_wide_integer_type = uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;
    using local_value_type        = typename local_wide_integer_type::representation_type::value_type;

    auto bpos   = static_cast<unsigned_fast_type>(0U);
    auto offset = static_cast<unsigned_fast_type>(x.crepresentation().size() - 1U);

    for(auto ri = x.crepresentation().crbegin(); ri != x.crepresentation().crend(); ++ri, --offset) // NOLINT(altera-id-dependent-backward-branch)
    {
      const auto vr = static_cast<local_value_type>(*ri & (std::numeric_limits<local_value_type>::max)());

      if(vr != static_cast<local_value_type>(0U))
      {
        bpos = static_cast<unsigned_fast_type>
               (
                    detail::msb_helper(*ri)
                  + static_cast<unsigned_fast_type>(static_cast<unsigned_fast_type>(std::numeric_limits<local_value_type>::digits) * offset)
               );

        break;
      }
    }

    return bpos;
  }

  template<const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  constexpr auto abs(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& x) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned>
  {
    using local_wide_integer_type = uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;

    return ((!local_wide_integer_type::is_neg(x)) ? x : -x);
  }

  template<const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  WIDE_INTEGER_CONSTEXPR auto sqrt(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& m) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned>
  {
    // Calculate the square root.

    using local_wide_integer_type = uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;

    local_wide_integer_type s;

    if(m.is_zero() || local_wide_integer_type::is_neg(m))
    {
      s = local_wide_integer_type(static_cast<std::uint_fast8_t>(0U));
    }
    else
    {
      // Obtain the initial guess via algorithms
      // involving the position of the msb.
      const unsigned_fast_type msb_pos = msb(m);

      // Obtain the initial value.
      const unsigned_fast_type left_shift_amount =
        ((static_cast<unsigned_fast_type>(msb_pos % 2U) == 0U)
          ? static_cast<unsigned_fast_type>(1U + static_cast<unsigned_fast_type>((msb_pos + 0U) / 2U))
          : static_cast<unsigned_fast_type>(1U + static_cast<unsigned_fast_type>((msb_pos + 1U) / 2U)));

      local_wide_integer_type
      u
      (
        local_wide_integer_type(static_cast<std::uint_fast8_t>(1U)) << left_shift_amount
      );

      // Perform the iteration for the square root.
      // See Algorithm 1.13 SqrtInt, Sect. 1.5.1
      // in R.P. Brent and Paul Zimmermann, "Modern Computer Arithmetic",
      // Cambridge University Press, 2011.

      for(auto i = static_cast<unsigned_fast_type>(0U); i < static_cast<unsigned_fast_type>(UINT8_C(64)); ++i)
      {
        s = u;

        u = (s + (m / s)) >> 1;

        if(u >= s)
        {
          break;
        }
      }
    }

    return s;
  }

  template<const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  WIDE_INTEGER_CONSTEXPR auto cbrt(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& m) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned> // NOLINT(misc-no-recursion)
  {
    // Calculate the cube root.

    using local_wide_integer_type = uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;

    local_wide_integer_type s;

    if(local_wide_integer_type::is_neg(m))
    {
      s = -cbrt(-m);
    }
    else if(m.is_zero())
    {
      s = local_wide_integer_type(static_cast<std::uint_fast8_t>(0U));
    }
    else
    {
      // Obtain the initial guess via algorithms
      // involving the position of the msb.
      const unsigned_fast_type msb_pos = msb(m);

      // Obtain the initial value.
      const auto msb_pos_mod_3 = static_cast<unsigned_fast_type>(msb_pos % UINT8_C(3));

      const unsigned_fast_type left_shift_amount =
        ((msb_pos_mod_3 == 0U)
          ? static_cast<unsigned_fast_type>(1U + static_cast<unsigned_fast_type>((msb_pos +                  0U ) / 3U))
          : static_cast<unsigned_fast_type>(1U + static_cast<unsigned_fast_type>((msb_pos + (3U - msb_pos_mod_3)) / 3U)));

      local_wide_integer_type u(local_wide_integer_type(static_cast<std::uint_fast8_t>(1U)) << left_shift_amount);

      // Perform the iteration for the k'th root.
      // See Algorithm 1.14 RootInt, Sect. 1.5.2
      // in R.P. Brent and Paul Zimmermann, "Modern Computer Arithmetic",
      // Cambridge University Press, 2011.

      const auto three_minus_one = static_cast<unsigned_fast_type>(3U - 1U);

      for(auto i = static_cast<unsigned_fast_type>(0U); i < static_cast<unsigned_fast_type>(UINT8_C(64)); ++i)
      {
        s = u;

        local_wide_integer_type m_over_s_pow_3_minus_one = m;

        for(unsigned_fast_type j = 0U; j < 3U - 1U; ++j)
        {
          // Use a loop here to divide by s^(3 - 1) because
          // without a loop, s^(3 - 1) is likely to overflow.

          m_over_s_pow_3_minus_one /= s;
        }

        u = ((s * three_minus_one) + m_over_s_pow_3_minus_one) / 3U;

        if(u >= s)
        {
          break;
        }
      }
    }

    return s;
  }

  template<const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  WIDE_INTEGER_CONSTEXPR auto rootk(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& m, const std::uint_fast8_t k) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned>
  {
    // Calculate the k'th root.

    using local_wide_integer_type = uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;

    local_wide_integer_type s;

    if(k < 2U)
    {
      s = m;
    }
    else if(k == 2U)
    {
      s = sqrt(m);
    }
    else if(k == 3U)
    {
      s = cbrt(m);
    }
    else
    {
      if(m.is_zero() || local_wide_integer_type::is_neg(m))
      {
        s = local_wide_integer_type(static_cast<std::uint_fast8_t>(0U));
      }
      else
      {
        // Obtain the initial guess via algorithms
        // involving the position of the msb.
        const unsigned_fast_type msb_pos = msb(m);

        // Obtain the initial value.
        const unsigned_fast_type msb_pos_mod_k = msb_pos % k;

        const unsigned_fast_type left_shift_amount =
          ((msb_pos_mod_k == 0U)
            ? 1U + static_cast<unsigned_fast_type>((msb_pos +                 0U ) / k)
            : 1U + static_cast<unsigned_fast_type>((msb_pos + (k - msb_pos_mod_k)) / k));

        local_wide_integer_type u(local_wide_integer_type(static_cast<std::uint_fast8_t>(1U)) << left_shift_amount);

        // Perform the iteration for the k'th root.
        // See Algorithm 1.14 RootInt, Sect. 1.5.2
        // in R.P. Brent and Paul Zimmermann, "Modern Computer Arithmetic",
        // Cambridge University Press, 2011.

        const unsigned_fast_type k_minus_one(k - 1U);

        for(auto i = static_cast<unsigned_fast_type>(0U); i < static_cast<unsigned_fast_type>(UINT8_C(64)); ++i)
        {
          s = u;

          local_wide_integer_type m_over_s_pow_k_minus_one = m;

          for(unsigned_fast_type j = 0U; j < k - 1U; ++j)
          {
            // Use a loop here to divide by s^(k - 1) because
            // without a loop, s^(k - 1) is likely to overflow.

            m_over_s_pow_k_minus_one /= s;
          }

          u = ((s * k_minus_one) + m_over_s_pow_k_minus_one) / k;

          if(u >= s)
          {
            break;
          }
        }
      }
    }

    return s;
  }

  template<typename OtherIntegralTypeP,
           const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  WIDE_INTEGER_CONSTEXPR auto pow(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& b, const OtherIntegralTypeP& p) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned>
  {
    // Calculate (b ^ p).
    using local_wide_integer_type = uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;
    using local_limb_type         = typename local_wide_integer_type::limb_type;

    local_wide_integer_type result;
    auto p0(static_cast<local_limb_type>(p));

    if((p0 == 0U) && (p == OtherIntegralTypeP(0)))
    {
      result = local_wide_integer_type(static_cast<std::uint8_t>(1U));
    }
    else if((p0 == 1U) && (p == OtherIntegralTypeP(1)))
    {
      result = b;
    }
    else if((p0 == 2U) && (p == OtherIntegralTypeP(2)))
    {
      result  = b;
      result *= b;
    }
    else
    {
      result = local_wide_integer_type(static_cast<std::uint8_t>(1U));

      local_wide_integer_type y      (b);
      local_wide_integer_type p_local(p);

      while(((p0 = static_cast<local_limb_type>(p_local)) != 0U) || (p_local != 0U)) // NOLINT(altera-id-dependent-backward-branch)
      {
        if((p0 & 1U) != 0U)
        {
          result *= y;
        }

        y *= y;

        p_local >>= 1;
      }
    }

    return result;
  }

  template<typename OtherIntegralTypeP,
           typename OtherIntegralTypeM,
           const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  WIDE_INTEGER_CONSTEXPR auto powm(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& b,
                                   const OtherIntegralTypeP& p,
                                   const OtherIntegralTypeM& m) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned>
  {
    // Calculate (b ^ p) % m.

    using local_normal_width_type = uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;
    using local_double_width_type = typename local_normal_width_type::double_width_type;
    using local_limb_type         = typename local_normal_width_type::limb_type;

          local_normal_width_type result;
          local_double_width_type y      (b);
    const local_double_width_type m_local(m);
          auto                    p0     (static_cast<local_limb_type>(p));

    if((p0 == 0U) && (p == OtherIntegralTypeP(0)))
    {
      result = local_normal_width_type((m != 1U) ? static_cast<std::uint8_t>(1U) : static_cast<std::uint8_t>(0U));
    }
    else if((p0 == 1U) && (p == OtherIntegralTypeP(1)))
    {
      result = b % m;
    }
    else if((p0 == 2U) && (p == OtherIntegralTypeP(2)))
    {
      y *= y;
      y %= m_local;

      result = local_normal_width_type(y);
    }
    else
    {
      local_double_width_type x      (static_cast<std::uint8_t>(1U));
      OtherIntegralTypeP      p_local(p);

      while(((p0 = static_cast<local_limb_type>(p_local)) != 0U) || (p_local != static_cast<OtherIntegralTypeP>(0))) // NOLINT(altera-id-dependent-backward-branch)
      {
        if((p0 & 1U) != 0U)
        {
          x *= y;
          x %= m_local;
        }

        y *= y;
        y %= m_local;

        p_local >>= 1U; // NOLINT(hicpp-signed-bitwise)
      }

      result = local_normal_width_type(x);
    }

    return result;
  }

  namespace detail {

  template<typename UnsignedShortType>
  WIDE_INTEGER_CONSTEXPR auto integer_gcd_reduce_short(UnsignedShortType u, UnsignedShortType v) -> UnsignedShortType
  {
    // This implementation of GCD reduction is based on an
    // adaptation of existing code from Boost.Multiprecision.

    for(;;)
    {
      if(u > v)
      {
        std::swap(u, v);
      }

      if(u == v)
      {
        break;
      }

      v  -= u;
      v >>= detail::lsb_helper(v);
    }

    return u;
  }

  template<typename UnsignedLargeType>
  WIDE_INTEGER_CONSTEXPR auto integer_gcd_reduce_large(UnsignedLargeType u, UnsignedLargeType v) -> UnsignedLargeType
  {
    // This implementation of GCD reduction is based on an
    // adaptation of existing code from Boost.Multiprecision.

    using local_ularge_type = UnsignedLargeType;
    using local_ushort_type = typename detail::uint_type_helper<static_cast<size_t>(std::numeric_limits<local_ularge_type>::digits / 2)>::exact_unsigned_type;

    for(;;)
    {
      if(u > v)
      {
        std::swap(u, v);
      }

      if(u == v)
      {
        break;
      }

      if(v <= static_cast<local_ularge_type>((std::numeric_limits<local_ushort_type>::max)()))
      {
        u = integer_gcd_reduce_short(static_cast<local_ushort_type>(v),
                                     static_cast<local_ushort_type>(u));

        break;
      }

      v -= u;

      while(static_cast<std::uint_fast8_t>(static_cast<std::uint_fast8_t>(v) & UINT8_C(1)) == UINT8_C(0)) // NOLINT(hicpp-signed-bitwise,altera-id-dependent-backward-branch)
      {
        v >>= 1U;
      }
    }

    return u;
  }

  } // namespace detail

  template<const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  WIDE_INTEGER_CONSTEXPR auto gcd(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& a, // NOLINT(readability-function-cognitive-complexity,bugprone-easily-swappable-parameters)
                                  const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& b) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned>
  {
    // This implementation of GCD is an adaptation
    // of existing code from Boost.Multiprecision.

    using local_wide_integer_type = uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;
    using local_ushort_type       = typename local_wide_integer_type::limb_type;
    using local_ularge_type       = typename local_wide_integer_type::double_limb_type;

    const bool u_is_neg = local_wide_integer_type::is_neg(a);
    const bool v_is_neg = local_wide_integer_type::is_neg(b);

    local_wide_integer_type u((!u_is_neg) ? a : -a);
    local_wide_integer_type v((!v_is_neg) ? b : -b);

    local_wide_integer_type result;

    if(u == v)
    {
      // This handles cases having (u = v) and also (u = v = 0).
      result = u;
    }

    if((static_cast<local_ushort_type>(v) == 0U) && (v == 0U))
    {
      // This handles cases having (v = 0) with (u != 0).
      result = u;
    }

    if((static_cast<local_ushort_type>(u) == 0U) && (u == 0U))
    {
      // This handles cases having (u = 0) with (v != 0).
      result = v;
    }
    else
    {
      // Now we handle cases having (u != 0) and (v != 0).

      // Let shift := lg K, where K is the greatest
      // power of 2 dividing both u and v.

      const unsigned_fast_type u_shift = lsb(u);
      const unsigned_fast_type v_shift = lsb(v);

      const unsigned_fast_type left_shift_amount = (std::min)(u_shift, v_shift);

      u >>= u_shift;
      v >>= v_shift;

      for(;;)
      {
        // Now u and v are both odd, so diff(u, v) is even.
        // Let u = min(u, v), v = diff(u, v) / 2.

        if(u > v)
        {
          swap(u, v);
        }

        if(u == v)
        {
          break;
        }

        if(v <= (std::numeric_limits<local_ularge_type>::max)())
        {
          if(v <= (std::numeric_limits<local_ushort_type>::max)())
          {
            u = detail::integer_gcd_reduce_short(*(v.crepresentation().cbegin() + 0U),
                                                 *(u.crepresentation().cbegin() + 0U));
          }
          else
          {
            const auto my_v_hi =
              static_cast<local_ushort_type>
              (
                (v.crepresentation().size() >= static_cast<typename local_wide_integer_type::representation_type::size_type>(2U))
                  ? static_cast<local_ushort_type>(*(v.crepresentation().cbegin() + 1U))
                  : static_cast<local_ushort_type>(0U)
              );

            const auto my_u_hi =
              static_cast<local_ushort_type>
              (
                (u.crepresentation().size() >= static_cast<typename local_wide_integer_type::representation_type::size_type>(2U))
                  ? static_cast<local_ushort_type>(*(u.crepresentation().cbegin() + 1U))
                  : static_cast<local_ushort_type>(0U)
              );

            const local_ularge_type v_large = detail::make_large(*(v.crepresentation().cbegin() + 0U), my_v_hi);
            const local_ularge_type u_large = detail::make_large(*(u.crepresentation().cbegin() + 0U), my_u_hi);

            u = detail::integer_gcd_reduce_large(v_large, u_large);
          }

          break;
        }

        v  -= u;
        v >>= lsb(v);
      }

      result = (u << left_shift_amount);
    }

    return ((u_is_neg == v_is_neg) ? result : -result);
  }

  template<typename UnsignedShortType>
  WIDE_INTEGER_CONSTEXPR auto gcd(const UnsignedShortType& u, const UnsignedShortType& v) -> std::enable_if_t<(   (std::is_integral<UnsignedShortType>::value)
                                                                                                               && (std::is_unsigned<UnsignedShortType>::value)), UnsignedShortType>
  {
    UnsignedShortType result;

    if(u > v)
    {
      result = gcd(v, u);
    }

    if(u == v)
    {
      // This handles cases having (u = v) and also (u = v = 0).
      result = u;
    }

    if(v == 0U)
    {
      // This handles cases having (v = 0) with (u != 0).
      result = u;
    }

    if(u == 0U)
    {
      // This handles cases having (u = 0) with (v != 0).
      result = v;
    }
    else
    {
      result = detail::integer_gcd_reduce_short(u, v);
    }

    return result;
  }

  namespace detail {

  template<typename IntegerType>
  WIDE_INTEGER_CONSTEXPR auto lcm_impl(const IntegerType& a, const IntegerType& b) -> IntegerType
  {
    using local_integer_type = IntegerType;

    using std::abs;

    const local_integer_type ap = abs(a);
    const local_integer_type bp = abs(b);

    const bool a_is_greater_than_b = (ap > bp);

    const local_integer_type gcd_of_ab = gcd(a, b);

    return (a_is_greater_than_b ? ap * (bp / gcd_of_ab)
                                : bp * (ap / gcd_of_ab));
  }

  } // namespace detail

  template<const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  WIDE_INTEGER_CONSTEXPR auto lcm(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& a,
                                  const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& b) -> uintwide_t<Width2, LimbType, AllocatorType, IsSigned>
  {
    return detail::lcm_impl(a, b);
  }

  template<typename UnsignedShortType>
  WIDE_INTEGER_CONSTEXPR auto lcm(const UnsignedShortType& a, const UnsignedShortType& b) -> std::enable_if_t<(   (std::is_integral<UnsignedShortType>::value)
                                                                                                               && (std::is_unsigned<UnsignedShortType>::value)), UnsignedShortType>
  {
    return detail::lcm_impl(a, b);
  }

  template<const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  class uniform_int_distribution
  {
  public:
    using result_type = uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;

    struct param_type
    {
    public:
      explicit WIDE_INTEGER_CONSTEXPR
        param_type
        (
          const result_type& p_a = (std::numeric_limits<result_type>::min)(), // NOLINT(modernize-pass-by-value)
          const result_type& p_b = (std::numeric_limits<result_type>::max)()  // NOLINT(modernize-pass-by-value)
        ) : param_a(p_a),
            param_b(p_b) { }

      WIDE_INTEGER_CONSTEXPR param_type(const param_type& other) : param_a(other.param_a),
                                                                   param_b(other.param_b) { }

      WIDE_INTEGER_CONSTEXPR param_type(param_type&& other) noexcept
        : param_a(static_cast<result_type&&>(other.param_a)),
          param_b(static_cast<result_type&&>(other.param_b)) { }

      ~param_type() = default;

      WIDE_INTEGER_CONSTEXPR auto operator=(const param_type& other) -> param_type& // NOLINT(cert-oop54-cpp)
      {
        if(this != &other)
        {
          param_a = other.param_a;
          param_b = other.param_b;
        }

        return *this;
      }

      WIDE_INTEGER_CONSTEXPR auto operator=(param_type&& other) noexcept -> param_type&
      {
        param_a = other.param_a;
        param_b = other.param_b;

        return *this;
      }

      WIDE_INTEGER_NODISCARD constexpr auto get_a() const -> result_type { return param_a; }
      WIDE_INTEGER_NODISCARD constexpr auto get_b() const -> result_type { return param_b; }

      WIDE_INTEGER_CONSTEXPR auto set_a(const result_type& p_a) -> void { param_a = p_a; }
      WIDE_INTEGER_CONSTEXPR auto set_b(const result_type& p_b) -> void { param_b = p_b; }

    private:
      result_type param_a; // NOLINT(readability-identifier-naming)
      result_type param_b; // NOLINT(readability-identifier-naming)

      friend inline constexpr auto operator==(const param_type& lhs,
                                              const param_type& rhs) -> bool
      {
        return (   (lhs.param_a == rhs.param_a)
                && (lhs.param_b == rhs.param_b));
      }

      friend inline constexpr auto operator!=(const param_type& lhs,
                                              const param_type& rhs) -> bool
      {
        return (   (lhs.param_a != rhs.param_a)
                || (lhs.param_b != rhs.param_b));
      }
    };

    explicit WIDE_INTEGER_CONSTEXPR
      uniform_int_distribution
      (
        const result_type& p_a = (std::numeric_limits<result_type>::min)(),
        const result_type& p_b = (std::numeric_limits<result_type>::max)()
      ) : my_params(p_a, p_b) { }

    explicit WIDE_INTEGER_CONSTEXPR uniform_int_distribution(const param_type& other_params)
      : my_params(other_params) { }

    WIDE_INTEGER_CONSTEXPR uniform_int_distribution(const uniform_int_distribution& other_distribution) = delete;

    WIDE_INTEGER_CONSTEXPR uniform_int_distribution(uniform_int_distribution&& other) noexcept : my_params(other.my_params) { }

    ~uniform_int_distribution() = default;

    auto WIDE_INTEGER_CONSTEXPR operator=(const uniform_int_distribution& other) -> uniform_int_distribution& // NOLINT(cert-oop54-cpp)
    {
      if(this != &other)
      {
        my_params = other.my_params;
      }

      return *this;
    }

    auto WIDE_INTEGER_CONSTEXPR operator=(uniform_int_distribution&& other) noexcept -> uniform_int_distribution&
    {
      my_params = other.my_params;

      return *this;
    }

    auto WIDE_INTEGER_CONSTEXPR param(const param_type& new_params) -> void
    {
      my_params = new_params;
    }

    WIDE_INTEGER_NODISCARD auto param() const -> const param_type& { return my_params; }

    WIDE_INTEGER_NODISCARD auto a() const -> result_type { return my_params.get_a(); }
    WIDE_INTEGER_NODISCARD auto b() const -> result_type { return my_params.get_b(); }

    template<typename GeneratorType,
             const int GeneratorResultBits = std::numeric_limits<typename GeneratorType::result_type>::digits>
    WIDE_INTEGER_CONSTEXPR auto operator()(GeneratorType& generator) -> result_type
    {
      return generate<GeneratorType, GeneratorResultBits>
             (
               generator,
               my_params
             );
    }

    template<typename GeneratorType,
             const int GeneratorResultBits = std::numeric_limits<typename GeneratorType::result_type>::digits>
    WIDE_INTEGER_CONSTEXPR auto operator()(      GeneratorType& input_generator,
                                           const param_type&    input_params) -> result_type
    {
      return generate<GeneratorType, GeneratorResultBits>
             (
               input_generator,
               input_params
             );
    }

  private:
    param_type my_params; // NOLINT(readability-identifier-naming)

    template<typename GeneratorType,
             const int GeneratorResultBits = std::numeric_limits<typename GeneratorType::result_type>::digits>
    WIDE_INTEGER_CONSTEXPR auto generate(      GeneratorType& input_generator,
                                         const param_type&    input_params) const -> result_type
    {
      // Generate random numbers r, where a <= r <= b.

      auto result = static_cast<result_type>(static_cast<std::uint8_t>(0U));

      using local_limb_type = typename result_type::limb_type;

      using generator_result_type = typename GeneratorType::result_type;

      constexpr auto digits_generator_result_type = static_cast<std::uint32_t>(GeneratorResultBits);

      static_assert((digits_generator_result_type % UINT32_C(8)) == UINT32_C(0),
                    "Error: Generator result type must have a multiple of 8 bits.");

      constexpr auto digits_limb_ratio = 
        static_cast<std::uint32_t>(std::numeric_limits<local_limb_type>::digits / 8U);

      constexpr auto digits_gtor_ratio = static_cast<std::uint32_t>(digits_generator_result_type / 8U);

      generator_result_type value = generator_result_type();

      auto it = result.representation().begin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto)

      unsigned_fast_type j = 0U;

      while(it != result.representation().end()) // NOLINT(altera-id-dependent-backward-branch)
      {
        if((j % digits_gtor_ratio) == 0U)
        {
          value = input_generator();
        }

        const auto next_byte =
          static_cast<std::uint8_t>(value >> static_cast<unsigned>(static_cast<unsigned_fast_type>(j % digits_gtor_ratio) * static_cast<unsigned_fast_type>(UINT8_C(8))));

        *it =
          static_cast<typename result_type::limb_type>
          (
            *it | static_cast<local_limb_type>(static_cast<local_limb_type>(next_byte) << static_cast<unsigned>(static_cast<unsigned_fast_type>(j % digits_limb_ratio) * static_cast<unsigned_fast_type>(UINT8_C(8))))
          );

        ++j;

        if(static_cast<unsigned_fast_type>(j % digits_limb_ratio) == static_cast<unsigned_fast_type>(0U))
        {
          ++it;
        }
      }

      if(   (input_params.get_a() != (std::numeric_limits<result_type>::min)())
         || (input_params.get_b() != (std::numeric_limits<result_type>::max)()))
      {
        // Note that this restricts the range r to:
        //   r = {[input_generator() % ((b - a) + 1)] + a}

        result_type range(input_params.get_b() - input_params.get_a());

        ++range;

        result %= range;
        result += input_params.get_a();
      }

      return result;
    }
  };

  template<const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  constexpr auto operator==(const uniform_int_distribution<Width2, LimbType, AllocatorType, IsSigned>& lhs,
                            const uniform_int_distribution<Width2, LimbType, AllocatorType, IsSigned>& rhs) -> bool
  {
    return (lhs.param() == rhs.param());
  }

  template<const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  constexpr auto operator!=(const uniform_int_distribution<Width2, LimbType, AllocatorType, IsSigned>& lhs,
                            const uniform_int_distribution<Width2, LimbType, AllocatorType, IsSigned>& rhs) -> bool
  {
    return (lhs.param() != rhs.param());
  }

  template<typename DistributionType,
           typename GeneratorType,
           const size_t Width2,
           typename LimbType,
           typename AllocatorType,
           const bool IsSigned>
  auto miller_rabin(const uintwide_t<Width2, LimbType, AllocatorType, IsSigned>& n, // NOLINT(readability-function-cognitive-complexity)
                    const unsigned_fast_type                                     number_of_trials,
                          DistributionType&                                      distribution,
                          GeneratorType&                                         generator) -> bool
  {
    // This Miller-Rabin primality test is loosely based on
    // an adaptation of some code from Boost.Multiprecision.
    // The Boost.Multiprecision code can be found here:
    // https://www.boost.org/doc/libs/1_76_0/libs/multiprecision/doc/html/boost_multiprecision/tut/primetest.html

    // Note: Some comments in this subroutine use the Wolfram Language(TM).
    // These can be exercised at the web links to WolframAlpha(R) provided

    using local_wide_integer_type = uintwide_t<Width2, LimbType, AllocatorType, IsSigned>;
    using local_limb_type         = typename local_wide_integer_type::limb_type;

    const local_wide_integer_type np((!local_wide_integer_type::is_neg(n)) ? n : -n);

    {
      const auto n0 = static_cast<local_limb_type>(np);

      if((n0 & 1U) == 0U)
      {
        // Not prime because n is even.
        return false;
      }

      if((n0 <= static_cast<local_limb_type>(UINT8_C(227))) && (np <= static_cast<local_limb_type>(UINT8_C(227))))
      {
        if((n0 == static_cast<local_limb_type>(UINT8_C(2))) && (np == static_cast<local_limb_type>(UINT8_C(2))))
        {
          // Trivial special case of (n = 2).
          return true;
        }

        // Exclude pure small primes from 3...227.
        // Table[Prime[i], {i, 2, 49}] =
        // {
        //     3,   5,   7,  11,  13,  17,  19,  23,
        //    29,  31,  37,  41,  43,  47,  53,  59,
        //    61,  67,  71,  73,  79,  83,  89,  97,
        //   101, 103, 107, 109, 113, 127, 131, 137,
        //   139, 149, 151, 157, 163, 167, 173, 179,
        //   181, 191, 193, 197, 199, 211, 223, 227
        // }
        // See also:
        // https://www.wolframalpha.com/input/?i=Table%5BPrime%5Bi%5D%2C+%7Bi%2C+2%2C+49%7D%5D

        constexpr std::array<local_limb_type, 48U> small_primes = 
        {{
          UINT8_C(  3), UINT8_C(  5), UINT8_C(  7), UINT8_C( 11), UINT8_C( 13), UINT8_C( 17), UINT8_C( 19), UINT8_C( 23),
          UINT8_C( 29), UINT8_C( 31), UINT8_C( 37), UINT8_C( 41), UINT8_C( 43), UINT8_C( 47), UINT8_C( 53), UINT8_C( 59),
          UINT8_C( 61), UINT8_C( 67), UINT8_C( 71), UINT8_C( 73), UINT8_C( 79), UINT8_C( 83), UINT8_C( 89), UINT8_C( 97),
          UINT8_C(101), UINT8_C(103), UINT8_C(107), UINT8_C(109), UINT8_C(113), UINT8_C(127), UINT8_C(131), UINT8_C(137),
          UINT8_C(139), UINT8_C(149), UINT8_C(151), UINT8_C(157), UINT8_C(163), UINT8_C(167), UINT8_C(173), UINT8_C(179),
          UINT8_C(181), UINT8_C(191), UINT8_C(193), UINT8_C(197), UINT8_C(199), UINT8_C(211), UINT8_C(223), UINT8_C(227)
        }};

        return std::binary_search(small_primes.cbegin(),
                                  small_primes.cend(),
                                  n0);
      }
    }

    // Check small factors.

    // Exclude small prime factors from { 3 ...  53 }.
    // Product[Prime[i], {i, 2, 16}] = 16294579238595022365
    // See also: https://www.wolframalpha.com/input/?i=Product%5BPrime%5Bi%5D%2C+%7Bi%2C+2%2C+16%7D%5D
    {
      constexpr std::uint64_t pp0 = UINT64_C(16294579238595022365);

      const auto m0 = static_cast<std::uint64_t>(np % pp0);

      if(detail::integer_gcd_reduce_large(m0, pp0) != 1U)
      {
        return false;
      }
    }

    // Exclude small prime factors from { 59 ... 101 }.
    // Product[Prime[i], {i, 17, 26}] = 7145393598349078859
    // See also: https://www.wolframalpha.com/input/?i=Product%5BPrime%5Bi%5D%2C+%7Bi%2C+17%2C+26%7D%5D
    {
      constexpr std::uint64_t pp1 = UINT64_C(7145393598349078859);

      const auto m1 = static_cast<std::uint64_t>(np % pp1);

      if(detail::integer_gcd_reduce_large(m1, pp1) != 1U)
      {
        return false;
      }
    }

    // Exclude small prime factors from { 103 ... 149 }.
    // Product[Prime[i], {i, 27, 35}] = 6408001374760705163
    // See also: https://www.wolframalpha.com/input/?i=Product%5BPrime%5Bi%5D%2C+%7Bi%2C+27%2C+35%7D%5D
    {
      constexpr std::uint64_t pp2 = UINT64_C(6408001374760705163);

      const auto m2 = static_cast<std::uint64_t>(np % pp2);

      if(detail::integer_gcd_reduce_large(m2, pp2) != 1U)
      {
        return false;
      }
    }

    // Exclude small prime factors from { 151 ... 191 }.
    // Product[Prime[i], {i, 36, 43}] = 690862709424854779
    // See also: https://www.wolframalpha.com/input/?i=Product%5BPrime%5Bi%5D%2C+%7Bi%2C+36%2C+43%7D%5D
    {
      constexpr std::uint64_t pp3 = UINT64_C(690862709424854779);

      const auto m3 = static_cast<std::uint64_t>(np % pp3);

      if(detail::integer_gcd_reduce_large(m3, pp3) != 1U)
      {
        return false;
      }
    }

    // Exclude small prime factors from { 193 ... 227 }.
    // Product[Prime[i], {i, 44, 49}] = 80814592450549
    // See also: https://www.wolframalpha.com/input/?i=Product%5BPrime%5Bi%5D%2C+%7Bi%2C+44%2C+49%7D%5D
    {
      constexpr std::uint64_t pp4 = UINT64_C(80814592450549);

      const auto m4 = static_cast<std::uint64_t>(np % pp4);

      if(detail::integer_gcd_reduce_large(m4, pp4) != 1U)
      {
        return false;
      }
    }

    const local_wide_integer_type nm1(np - 1U);

    // Since we have already excluded all small factors
    // up to and including 227, n is greater than 227.

    {
      // Perform a single Fermat test which will
      // exclude many non-prime candidates.

      const local_wide_integer_type fn = powm(local_wide_integer_type(static_cast<local_limb_type>(228U)), nm1, np);

      const auto fn0 = static_cast<local_limb_type>(fn);

      if((fn0 != 1U) && (fn != 1U))
      {
        return false;
      }
    }

    const unsigned_fast_type k = lsb(nm1);

    const local_wide_integer_type q = nm1 >> k;

    using local_param_type = typename DistributionType::param_type;

    const local_param_type params(local_wide_integer_type(2U), np - 2U);

    bool is_probably_prime = true;

    auto i = static_cast<unsigned_fast_type>(0U);

    local_wide_integer_type x;
    local_wide_integer_type y;

    // Execute the random trials.
    do
    {
      x = distribution(generator, params);
      y = powm(x, q, np);

      auto j = static_cast<unsigned_fast_type>(0U);

      while(y != nm1) // NOLINT(altera-id-dependent-backward-branch)
      {
        const local_limb_type y0(y);

        if((y0 == 1U) && (y == 1U))
        {
          if(j != 0U)
          {
            is_probably_prime = false;
          }
          else
          {
            break;
          }
        }
        else
        {
          ++j;

          if(j == k)
          {
            is_probably_prime = false;
          }
          else
          {
            y = powm(y, 2U, np);
          }
        }
      }

      ++i;
    }
    while((i < number_of_trials) && is_probably_prime);

    // The prime candidate is probably prime in the sense
    // of the very high probability resulting from Miller-Rabin.
    return is_probably_prime;
  }

  #if(__cplusplus >= 201703L)
  } // namespace math::wide_integer
  #else
  } // namespace wide_integer
  } // namespace math
  #endif

  WIDE_INTEGER_NAMESPACE_END

#endif // UINTWIDE_T_2018_10_02_H
