Document number: P0539R3
Project: Programming Language C++
Audience: SG6 Numerics, Library Evolution
 
Igor Klevanets <cerevra@yandex.ru>, <cerevra@yandex-team.ru>
Antony Polukhin <antoshkka@gmail.com>, <antoshkka@yandex-team.ru>
 
Date: 2018-01-31

A Proposal to add wide_int Template Class

Significant changes to P0539R2 are marked with blue.

Show deleted lines from P0539R2.

Green lines are notes for the editor or for the SG6 that must not be treated as part of the wording.

I. Introduction and Motivation

Current standard provides signed and unsigned int8_t, int16_t, int32_t, int64_t. It is usually enough for every day tasks, but sometimes appears a need in big numbers: for cryptography, IPv6, very big counters etc. Non-standard type __int128 which is provided by gcc and clang illuminates this need. But there is no cross-platform solution and no way to satisfy future needs in even more big numbers.

This is an attempt to solve the problem in a generic way on a library level and provide wording for P0104R0: Multi-Word Integer Operations and Types.

A proof of concept implementation available at: https://github.com/cerevra/int/tree/master/v3.

II. Changelog

Differences with P0539R2:

Differences with P0539R1:

Differences with P0539R0:

III. Design and paper limitations

wide_integer is designed to be as close as possible to built-in integral types:

wide_integer is not a metafunction. Such metafunctions are discussed in P0102R0.

wide_integer does not add noexcept to operations that have UB for built-in types. Operations that have UB traditionally are not marked with noexcept by LWG. Attempt to change that behavior may be done in separate paper.

In this proposal we concentrate on the wide_integer class that uses machine words under the cover. Such implementations allow you to get best performance and leave behind some design questions, like "Is (sizeof(wide_integer<X>) == X / CHAR_BITS) or not?".

However, we do not wish to shut the door close for extending the abilities of the wide_integer class. Some users may wish to see unit40_t, or unit48_t.

We insist on interface that allows specifying integers not representable by machine words count. Such extensions of functionality may be discussed in separate papers.

IV. Proposed wording

18.3.4 Class template numeric_limits[numeric.limits]

[Note to editor: Add the following sentence after the sentence "Specializations shall be provided for each arithmetic type, both floating-point and integer, including bool." (first sentence in fourth paragraph in [numeric.limits]) - end note]

Specializations shall be also provided for wide_integer type. [Note: If there is a built-in integral type Integral that has the same signedness and width as wide_integer<Bits, S>, then numeric_limits<wide_integer<Bits, S>> specialized in the same way as numeric_limits<Integral>- end note]

26.??.1 Header <wide_integer> synopsis[numeric.wide_integer.syn]

namespace std {

  // 26.??.2 class template wide_integer
  template<size_t Bits, typename S> class wide_integer;

  // 26.??.?? type traits specializations
  template<size_t Bits, typename S, size_t Bits2, typename S2>
  struct common_type<wide_integer<Bits, S>, wide_integer<Bits2, S2>>;

  template<size_t Bits, typename S, typename Arithmetic>
  struct common_type<wide_integer<Bits, S>, Arithmetic>;

  template<typename Arithmetic, size_t Bits, typename S>
  struct common_type<Arithmetic, wide_integer<Bits, S>>
    : common_type<wide_integer<Bits, S>, Arithmetic>
  ;

  // 26.??.?? unary operations
  template<size_t Bits, typename S> constexpr wide_integer<Bits, S> operator~(const wide_integer<Bits, S>& val) noexcept;
  template<size_t Bits, typename S> constexpr wide_integer<Bits, S> operator-(const wide_integer<Bits, S>& val) noexcept(is_unsigned_v<S>);
  template<size_t Bits, typename S> constexpr wide_integer<Bits, S> operator+(const wide_integer<Bits, S>& val) noexcept(is_unsigned_v<S>) noexcept;

  // 26.??.?? binary operations
  template<size_t Bits, typename S, size_t Bits2, typename S2>
    common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
    constexpr operator*(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs);

  template<size_t Bits, typename S, size_t Bits2, typename S2>
    common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
    constexpr operator/(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs);

  template<size_t Bits, typename S, size_t Bits2, typename S2>
    common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
    constexpr operator+(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept(is_unsigned_v<S>);

  template<size_t Bits, typename S, size_t Bits2, typename S2>
    common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
    constexpr operator-(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept(is_unsigned_v<S>);

  template<size_t Bits, typename S, size_t Bits2, typename S2>
    common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
    constexpr operator%(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs);

  template<size_t Bits, typename S, size_t Bits2, typename S2>
    common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
    constexpr operator&(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;

  template<size_t Bits, typename S, size_t Bits2, typename S2>
    common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
    constexpr operator|(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;

  template<size_t Bits, typename S, size_t Bits2, typename S2>
    common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
    constexpr  operator^(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;

  template<size_t Bits, typename S>
    common_type_t<wide_integer<Bits, S>, size_t>
    constexpr  operator<<(const wide_integer<Bits, S>& lhs, size_t rhs);

  template<size_t Bits, typename S>
    common_type_t<wide_integer<Bits, S>, size_t>
    constexpr  operator>>(const wide_integer<Bits, S>& lhs, size_t rhs);

  template<size_t Bits, typename S, size_t Bits2, typename S2>
    constexpr bool operator<(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;

  template<size_t Bits, typename S, size_t Bits2, typename S2>
    constexpr bool operator>(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;

  template<size_t Bits, typename S, size_t Bits2, typename S2>
    constexpr bool operator<=(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;

  template<size_t Bits, typename S, size_t Bits2, typename S2>
    constexpr bool operator>=(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;

  template<size_t Bits, typename S, size_t Bits2, typename S2>
    constexpr bool operator==(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;

  template<size_t Bits, typename S, size_t Bits2, typename S2>
    constexpr bool operator!=(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;

  // 26.??.?? numeric conversions
  template<size_t Bits, typename S> std::string to_string(const wide_integer<Bits, S>& val);
  template<size_t Bits, typename S> std::wstring to_wstring(const wide_integer<Bits, S>& val);

  // 26.??.?? iostream specializations
  template<class Char, class Traits, size_t Bits, typename S>
  basic_ostream<Char, Traits>& operator<<(basic_ostream<Char, Traits>& os, const wide_integer<Bits, S>& val);

  template<class Char, class Traits, size_t Bits, typename S>
  basic_istream<Char, Traits>& operator>>(basic_istream<Char, Traits>& is, wide_integer<Bits, S>& val) noexcept;

  // 26.??.?? hash support
  template<class T> struct hash;
  template<size_t Bits, typename S> struct hash<wide_integer<Bits, S>>;


  template <size_t Bits, typename S>
  to_chars_result to_chars(char* first,
                           char* last,
                           const wide_integer<Bits, S>& value,
                           int base = 10);

  template <size_t Bits, typename S>
  from_chars_result from_chars(const char* first,
                               const char* last,
                               wide_integer<Bits, S>& value,
                               int base = 10);

  template <size_t Bits>
  using wide_int = wide_integer<Bits, signed>;

  template <size_t Bits>
  using wide_uint = wide_integer<Bits, unsigned>

[Note to SG6: P0102R0 proposal with exact_2int, fast_2int, least_2int, exact_2uint, fast_2uint, least_2uint requires additional specializations for wide_integer. - end note]

  // optional literals
  inline namespace literals {
  inline namespace wide_int_literals {

  constexpr wide_int<128> operator "" _int128(const char*);
  constexpr wide_int<256> operator "" _int256(const char*);
  constexpr wide_int<512> operator "" _int512(const char*);
  constexpr wide_uint<128> operator "" _uint128(const char*);
  constexpr wide_uint<256> operator "" _uint256(const char*);
  constexpr wide_uint<512> operator "" _uint512(const char*);

  } // namespace wide_int_literals
  } // namespace literals
}

The header <wide_integer> defines class template wide_integer and a set of operators for representing and manipulating integers of specified width.

[Example:
    using int128_t = wide_int<128>;
    constexpr int128_t c = std::numeric_limits<int128_t>::min();
    static_assert(c == 0x80000000000000000000000000000000_uint128);

    int256_t a = 13;
    a += 0xFF;
    a *= 2.0;
    a -= 12_int128;
    assert(a > 0);
]

26.??.2 Template class wide_integer overview[numeric.wide_integer.overview]

namespace std {
  template<size_t Bits, typename S>
  class wide_integer {
  public:
    // 26.??.2.?? construct:
    constexpr wide_integer() noexcept = default;
    constexpr wide_integer(const wide_integer<Bits, S>& ) noexcept = default;
    template<typename Arithmetic> constexpr wide_integer(const Arithmetic& other) noexcept;
    template<size_t Bits2, typename S2> constexpr wide_integer(const wide_integer<Bits2, S2>& other) noexcept;

    // 26.??.2.?? assignment:
    constexpr wide_integer<Bits, S>& operator=(const wide_integer<Bits, S>& ) noexcept = default;
    template<typename Arithmetic>
    constexpr wide_integer<Bits, S>& operator=(const Arithmetic& other) noexcept;
    template<size_t Bits2, typename S2>
    constexpr wide_integer<Bits, S>& operator=(const wide_integer<Bits2, S2>& other) noexcept;

    // 26.??.2.?? compound assignment:
    template<typename Arithmetic>
    constexpr wide_integer<Bits, S>& operator*=(const Arithmetic&);
    template<size_t Bits2, typename S2>
    constexpr wide_integer<Bits, S>& operator*=(const wide_integer<Bits2, S2>&);

    template<typename Arithmetic>
    constexpr wide_integer<Bits, S>& operator/=(const Arithmetic&);
    template<size_t Bits2, typename S2>
    constexpr wide_integer<Bits, S>& operator/=(const wide_integer<Bits2, S2>&);

    template<typename Arithmetic>
    constexpr wide_integer<Bits, S>& operator+=(const Arithmetic&) noexcept(is_unsigned_v<S>);
    template<size_t Bits2, typename S2>
    constexpr wide_integer<Bits, S>& operator+=(const wide_integer<Bits2, S2>&) noexcept(is_unsigned_v<S>);

    template<typename Arithmetic>
    constexpr wide_integer<Bits, S>& operator-=(const Arithmetic&) noexcept(is_unsigned_v<S>);
    template<size_t Bits2, typename S2>
    constexpr wide_integer<Bits, S>& operator-=(const wide_integer<Bits2, S2>&) noexcept(is_unsigned_v<S>);

    template<typename Integral>
    constexpr wide_integer<Bits, S>& operator%=(const Integral&);
    template<size_t Bits2, typename S2>
    constexpr wide_integer<Bits, S>& operator%=(const wide_integer<Bits2, S2>&);

    template<typename Integral>
    constexpr wide_integer<Bits, S>& operator&=(const Integral&) noexcept;
    template<size_t Bits2, typename S2>
    constexpr wide_integer<Bits, S>& operator&=(const wide_integer<Bits2, S2>&) noexcept;

    template<typename Integral>
    constexpr wide_integer<Bits, S>& operator|=(const Integral&) noexcept;
    template<size_t Bits2, typename S2>
    constexpr wide_integer<Bits, S>& operator|=(const wide_integer<Bits2, S2>&) noexcept;

    template<typename Integral>
    constexpr wide_integer<Bits, S>& operator^=(const Integral&) noexcept;
    template<size_t Bits2, typename S2>
    constexpr wide_integer<Bits, S>& operator^=(const wide_integer<Bits2, S2>&) noexcept;

    template<typename Integral>
    constexpr wide_integer<Bits, S>& operator<<=(const Integral&);
    template<size_t Bits2, typename S2>
    constexpr wide_integer<Bits, S>& operator<<=(const wide_integer<Bits2, S2>&);

    template<typename Integral>
    constexpr wide_integer<Bits, S>& operator>>=(const Integral&) noexcept;
    template<size_t Bits2, typename S2>
    constexpr wide_integer<Bits, S>& operator>>=(const wide_integer<Bits2, S2>&) noexcept;

    constexpr wide_integer<Bits, S>& operator++() noexcept(is_unsigned_v<S>);
    constexpr wide_integer<Bits, S> operator++(int) noexcept(is_unsigned_v<S>);
    constexpr wide_integer<Bits, S>& operator--() noexcept(is_unsigned_v<S>);
    constexpr wide_integer<Bits, S> operator--(int) noexcept(is_unsigned_v<S>);

    // 26.??.2.?? observers:
    template <typename Arithmetic> constexpr operator Arithmetic() const noexcept;
    constexpr explicit operator bool() const noexcept;

  private:
    byte data[Bits / CHAR_BITS]; // exposition only
  };
}

The class template wide_integer<size_t Bits, typename S> is a trivial standard layout class that behaves as an integer type of a compile time specified bitness.

Template parameter Bits specifies exact bits count to store the integer value. Bits % (sizeof(int) * CHAR_BITS) is eqaul to 0. sizeof(wide_integer<Bits, unsigned>) and sizeof(wide_integer<Bits, signed>) are required to be equal to Bits * CHAR_BITS.

Template parameter S specifies signedness of the stored integer value and is either signed or unsigned.

26.??.2.?? wide_integer constructors [numeric.wide_integer.cons]

constexpr wide_integer() noexcept = default;
Effects: A Constructs an object with undefined value.
template<typename Arithmetic> constexpr wide_integer(const Arithmetic& other) noexcept;
Effects: Constructs an object from other using the integral conversion rules [conv.integral].
template<size_t Bits2, typename S2> constexpr wide_integer(const wide_integer<Bits2, S2>& other) noexcept;
Effects: Constructs an object from other using the integral conversion rules [conv.integral].

26.??.2.?? wide_integer assignments [numeric.wide_integer.assign]

template<typename Arithmetic>
constexpr wide_integer<Bits, S>& operator=(const Arithmetic& other) noexcept;
Effects: Constructs an object from other using the integral conversion rules [conv.integral].
template<size_t Bits2, typename S2>
constexpr wide_integer<Bits, S>& operator=(const wide_integer<Bits2, S2>& other) noexcept;
Effects: Constructs an object from other using the integral conversion rules [conv.integral].

26.??.2.?? wide_integer compound assignments [numeric.wide_integer.cassign]

template<size_t Bits2, typename S2> constexpr wide_integer<Bits, S>& operator*=(const wide_integer<Bits2, S2>&);
template<size_t Bits2, typename S2> constexpr wide_integer<Bits, S>& operator/=(const wide_integer<Bits2, S2>&);
template<size_t Bits2, typename S2> constexpr wide_integer<Bits, S>& operator+=(const wide_integer<Bits2, S2>&) noexcept(is_unsigned_v<S>);
template<size_t Bits2, typename S2> constexpr wide_integer<Bits, S>& operator-=(const wide_integer<Bits2, S2>&) noexcept(is_unsigned_v<S>);
template<size_t Bits2, typename S2> constexpr wide_integer<Bits, S>& operator%=(const wide_integer<Bits2, S2>&);
template<size_t Bits2, typename S2> constexpr wide_integer<Bits, S>& operator&=(const wide_integer<Bits2, S2>&) noexcept;
template<size_t Bits2, typename S2> constexpr wide_integer<Bits, S>& operator|=(const wide_integer<Bits2, S2>&) noexcept;
template<size_t Bits2, typename S2> constexpr wide_integer<Bits, S>& operator^=(const wide_integer<Bits2, S2>&) noexcept;
template<size_t Bits2, typename S2> constexpr wide_integer<Bits, S>& operator<<=(const wide_integer<Bits2, S2>&);
template<size_t Bits2, typename S2> constexpr wide_integer<Bits, S>& operator>>=(const wide_integer<Bits2, S2>&) noexcept;
constexpr wide_integer<Bits, S>& operator++() noexcept(is_unsigned_v<S>);
constexpr wide_integer<Bits, S> operator++(int) noexcept(is_unsigned_v<S>);
constexpr wide_integer<Bits, S>& operator--() noexcept(is_unsigned_v<S>);
constexpr wide_integer<Bits, S> operator--(int) noexcept(is_unsigned_v<S>);
Effects: Behavior of the above operators is similar to operators for built-in integral types.
template<typename Arithmetic> constexpr wide_integer<Bits, S>& operator*=(const Arithmetic&);
template<typename Arithmetic> constexpr wide_integer<Bits, S>& operator/=(const Arithmetic&);
template<typename Arithmetic> constexpr wide_integer<Bits, S>& operator+=(const Arithmetic&) noexcept(is_unsigned_v<S>);
template<typename Arithmetic> constexpr wide_integer<Bits, S>& operator-=(const Arithmetic&) noexcept(is_unsigned_v<S>);
Effects: As if an object wi of type wide_integer<Bits, S> was created from input value and the corresponding operator was called for *this and the wi.
template<typename Integral> constexpr wide_integer<Bits, S>& operator%=(const Integral&);
template<typename Integral> constexpr wide_integer<Bits, S>& operator&=(const Integral&) noexcept;
template<typename Integral> constexpr wide_integer<Bits, S>& operator|=(const Integral&) noexcept;
template<typename Integral> constexpr wide_integer<Bits, S>& operator^=(const Integral&) noexcept;
template<typename Integral> constexpr wide_integer<Bits, S>& operator<<=(const Integral&);
template<typename Integral> constexpr wide_integer<Bits, S>& operator>>=(const Integral&) noexcept;
Effects: As if an object wi of type wide_integer<Bits, S> was created from input value and the corresponding operator was called for *this and the wi.

26.??.2.?? wide_integer observers [numeric.wide_integer.observers]

template <typename Arithmetic> constexpr operator Arithmetic() const noexcept;
Returns: If Arithmetic type is an integral type then it is constructed from *this using the integral conversion rules [conv.integral]. Otherwise Arithmetic is constructed from *this using the floating-integral conversion rules [conv.fpint].
constexpr explicit operator bool() const noexcept;
Returns: true if *this is not equal to 0.

26.??.?? Specializations of common_type [numeric.wide_integer.traits.specializations]

template<size_t Bits, typename S, size_t Bits2, typename S2>
struct common_type<wide_integer<Bits, S>, wide_integer<Bits2, S2>> {
  using type = wide_integer<max(Bits, Bits2), see below>;
};

The signed template parameter indicated by this specialization is following:

[Note: common_type follows the usual arithmetic conversions design. - end note]

[Note to SG6: common_type attempts to follow the usual arithmetic conversions design here for interoperability between different numeric types. Following two specializations must be moved to a more generic place and enriched with usual arithmetic conversion rules for all the other numeric classes that specialize std::numeric_limits- end note]

template<size_t Bits, typename S, typename Arithmetic>
struct common_type<wide_integer<Bits, S>, Arithmetic> {
  using type = see below;
};
template<typename Arithmetic, size_t Bits, typename S>
struct common_type<Arithmetic, wide_integer<Bits, S>>
  : common_type<wide_integer<Bits, S>, Arithmetic>;

The member typedef type is following:

26.??.?? Unary operators [numeric.wide_integer.unary_ops]

template<size_t Bits, typename S> constexpr wide_integer<Bits, S> operator~(const wide_integer<Bits, S>& val) noexcept;
Returns: value with inverted significant bits of val.
template<size_t Bits, typename S> constexpr wide_integer<Bits, S> operator-(const wide_integer<Bits, S>& val) noexcept(is_unsigned_v<S>);
Returns: val *= -1 if S is true, otherwise the result is unspecified.
template<size_t Bits, typename S> constexpr wide_integer<Bits, S> operator+(const wide_integer<Bits, S>& val) noexcept(is_unsigned_v<S>);
Returns: val.

26.??.?? Binary operators [numeric.wide_integer.binary_ops]

In the function descriptions that follow, CT represents common_type_t<A, B>, where A and B are the types of the two arguments to the function.

template<size_t Bits, typename S, size_t Bits2, typename S2>
  common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
  constexpr operator*(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs);
Returns: CT(lhs) *= rhs.
template<size_t Bits, typename S, size_t Bits2, typename S2>
  common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
  constexpr operator/(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs);
Returns: CT(lhs) /= rhs.
template<size_t Bits, typename S, size_t Bits2, typename S2>
  common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
  constexpr operator+(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept(is_unsigned_v<S>);
Returns: CT(lhs) += rhs.
template<size_t Bits, typename S, size_t Bits2, typename S2>
  common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
  constexpr operator-(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept(is_unsigned_v<S>);
Returns: CT(lhs) -= rhs.
template<size_t Bits, typename S, size_t Bits2, typename S2>
  common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
  constexpr operator%(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs);
Returns: CT(lhs) %= rhs.
template<size_t Bits, typename S, size_t Bits2, typename S2>
  common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
  constexpr operator&(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;
Returns: CT(lhs) &= rhs.
template<size_t Bits, typename S, size_t Bits2, typename S2>
  common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
  constexpr operator|(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;
Returns: CT(lhs) |= rhs.
template<size_t Bits, typename S, size_t Bits2, typename S2>
  common_type_t<wide_integer<Bits, S>, wide_integer<Bits2, S2>>
  constexpr  operator^(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;
Returns: CT(lhs) ^= rhs.
template<size_t Bits, typename S>
  common_type_t<wide_integer<Bits, S>, size_t>
  constexpr operator<<(const wide_integer<Bits, S>& lhs, size_t rhs);
Returns: CT(lhs) <<= rhs.
template<size_t Bits, typename S>
  common_type_t<wide_integer<Bits, S>, size_t>
  constexpr operator>>(const wide_integer<Bits, S>& lhs, size_t rhs) noexcept;
Returns: CT(lhs) >>= rhs.
template<size_t Bits, typename S, size_t Bits2, typename S2>
  constexpr bool operator<(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;
Returns: true if value of CT(lhs) is less than the value of CT(rhs).
template<size_t Bits, typename S, size_t Bits2, typename S2>
  constexpr bool operator>(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;
Returns: true if value of CT(lhs) is greater than the value of CT(rhs).
template<size_t Bits, typename S, size_t Bits2, typename S2>
  constexpr bool operator<=(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;
Returns: true if value of CT(lhs) is equal or less than the value of CT(rhs).
template<size_t Bits, typename S, size_t Bits2, typename S2>
  constexpr bool operator>=(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;
Returns: true if value of CT(lhs) is equal or greater than the value of CT(rhs).
template<size_t Bits, typename S, size_t Bits2, typename S2>
  constexpr bool operator==(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;
Returns: true if significant bits of CT(lhs) and CT(rhs) are the same.
template<size_t Bits, typename S, size_t Bits2, typename S2>
  constexpr bool operator!=(const wide_integer<Bits, S>& lhs, const wide_integer<Bits2, S2>& rhs) noexcept;
Returns: !(CT(lhs) == CT(rhs)).

26.??.?? Numeric conversions [numeric.wide_integer.conversions]

template<size_t Bits, typename S> std::string to_string(const wide_integer<Bits, S>& val);
template<size_t Bits, typename S> std::wstring to_wstring(const wide_integer<Bits, S>& val);
Returns: Each function returns an object holding the character representation of the value of its argument. All the significant bits of the argument are outputed as a signed decimal in the style [-]dddd.
template <size_t Bits, typename S>
to_chars_result to_chars(char* first,
                       char* last,
                       const wide_integer<Bits, S>& value,
                       int base = 10);

Behavior of wide_integer overload is subject to the usual rules of primitive numeric output conversion functions [utility.to.chars].

template <size_t Bits, typename S>
from_chars_result from_chars(const char* first,
                           const char* last,
                           wide_integer<Bits, S>& value,
                           int base = 10);

Behavior of wide_integer overload is subject to the usual rules of primitive numeric input conversion functions [utility.from.chars].

26.??.?? iostream specializations [numeric.wide_integer.io]

template<class Char, class Traits, size_t Bits, typename S>
basic_ostream<Char, Traits>& operator<<(basic_ostream<Char, Traits>& os, const wide_integer<Bits, S>& val);
Effects: As if by: os << to_string(val).
Returns: os.
template<class Char, class Traits, size_t Bits, typename S>
basic_istream<Char, Traits>& operator>>(basic_istream<Char, Traits>& is, wide_integer<Bits, S>& val);
Effects: Extracts a wide_integer that is represented as a decimal number in the is. If bad input is encountered, calls is.setstate(ios_base::failbit) (which may throw ios::failure ([iostate.flags])).
Returns: is.

26.??.?? Hash support [numeric.wide_integer.hash]

template<size_t Bits, typename S> struct hash<wide_integer<Bits, S>>;

The specialization is enabled (20.14.14). If there is a built-in integral type Integral that has the same typename and width as wide_integer<Bits, S>, and wi is an object of type wide_integer<Bits, S>, then hash<wide_integer<MachineWords, S>>()(wi) == hash<Integral>()(Integral(wi)).

V. Feature-testing macro

For the purposes of SG10 we recommend the feature-testing macro name __cpp_lib_wide_integer.