The big_int Type
Design
The most important anchor points of the design of the big_int type are:
-
big_intshould be elastic; that is, it should grow to the necessary size required to fit the result of an arithmetic operation. -
big_intshould support custom allocators. -
big_intshould expose small-object optimization to the user.
This leads us to the following type declarations:
namespace beman::big_int {
// uint_multiprecision_t is the unsigned integer used to store each "limb"
// (machine-word digit) of the magnitude of a big_int.
#if ... // 32-bit limb platform detected
using uint_multiprecision_t = unsigned int;
#else // 64-bit limb platform detected
using uint_multiprecision_t = unsigned long long;
#endif
} // namespace beman::big_int
// min_inplace_bits is the number of bits of statically allocated storage that
// the basic_big_int will have available for the small-object optimization.
// Limb names the limb type, and shall be uint_multiprecision_t.
template <std::size_t min_inplace_bits,
class Limb = uint_multiprecision_t,
class Allocator = std::allocator<Limb>>
class basic_big_int;
// Default specialization with a stateless allocator.
using big_int = basic_big_int<64, uint_multiprecision_t, std::allocator<uint_multiprecision_t>>;
namespace pmr {
// Specialization parameterized on std::pmr::polymorphic_allocator.
template <std::size_t b, class L = uint_multiprecision_t>
using basic_big_int = beman::big_int::basic_big_int<b, L, std::pmr::polymorphic_allocator<L>>;
using big_int = basic_big_int</* same in-place bits as the default big_int */>;
} // namespace pmr
} // namespace beman::big_int
A basic_big_int stores its magnitude as a little-endian sequence of
uint_multiprecision_t limbs (the first limb holds the least significant bits)
together with a separate sign. Values whose magnitude fits within
inplace_bits bits are stored directly inside the object; larger values are
stored in allocator-provided dynamic storage.
The limb type is named by the Limb template parameter rather than being fixed by the
class, so that a future revision can change it without changing the signature of
basic_big_int. Limb shall be uint_multiprecision_t, and
Allocator::value_type shall be Limb; a program that instantiates
basic_big_int with any other limb type is ill-formed.
For a practical implementation the big_int does have a maximum size available as max_size().
Any operation that would need more storage than that, or the growth required by
an arithmetic, shift, bitwise, assignment, or parsing operation, etc., throws
std::length_error, or in an exception free environment std::abort().
The check happens before the allocator is consulted, so no allocation is attempted for a request that cannot be represented.
A request that is within the limit but cannot be served reports the allocator’s own failure (typically std::bad_alloc) instead.
During constant evaluation the same condition is a compile-time error rather than an exception, since a thrown exception is not a
constant expression.
Synopsis
In the synopsis below, the italicized names (arbitrary-arithmetic,
arbitrary-integer, signed-or-unsigned, cv-unqualified-arithmetic,
common-big-int-type, and common-big-int-type-with) are exposition-only
concepts and helper aliases used to constrain the interface.
common-big-int-type<L, R> denotes the basic_big_int specialization shared
by L and R when one operand is a basic_big_int and the other is either the
same specialization or a built-in signed or unsigned integer type.
template <std::size_t min_inplace_bits,
class Limb = uint_multiprecision_t,
class Allocator = std::allocator<Limb>>
class basic_big_int {
public:
// member types
using allocator_type = Allocator;
using size_type = std::size_t;
using pointer = std::allocator_traits<Allocator>::pointer;
using const_pointer = std::allocator_traits<Allocator>::const_pointer;
// member constants
static constexpr size_type inplace_capacity = /* number of in-place limbs */;
static constexpr size_type inplace_bits =
inplace_capacity * std::numeric_limits<Limb>::digits;
// [big.int.cons], construct/copy/destroy
constexpr basic_big_int() noexcept(noexcept(Allocator()));
constexpr explicit basic_big_int(const Allocator& a) noexcept;
constexpr basic_big_int(const basic_big_int& x);
constexpr basic_big_int(basic_big_int&& x) noexcept;
constexpr basic_big_int(const basic_big_int& x, const std::type_identity_t<Allocator>& a);
constexpr basic_big_int(basic_big_int&& x, const std::type_identity_t<Allocator>& a);
template <_arbitrary-arithmetic_ T>
requires(!std::same_as<std::remove_cvref_t<T>, basic_big_int>)
constexpr explicit(/* see below */) basic_big_int(T&& value) noexcept(/* see below */);
template <_arbitrary-arithmetic_ T>
constexpr basic_big_int(const T& value, const allocator_type& a) noexcept(/* see below */);
template <std::input_iterator I, std::sentinel_for<I> S>
requires _signed-or-unsigned_<std::iter_value_t<I>>
constexpr basic_big_int(I begin, S end, const allocator_type& a = allocator_type());
template <std::ranges::input_range R>
requires _signed-or-unsigned_<std::ranges::range_value_t<R>>
constexpr basic_big_int(std::from_range_t, R&& r, const allocator_type& a = allocator_type());
constexpr ~basic_big_int();
// [big.int.modifiers], assignment
constexpr basic_big_int& operator=(const basic_big_int& x);
constexpr basic_big_int& operator=(basic_big_int&& x) noexcept;
template <_arbitrary-integer_ T>
requires(!std::same_as<std::remove_cvref_t<T>, basic_big_int>)
constexpr basic_big_int& operator=(T&& x) noexcept(/* see below */);
template <class T> constexpr basic_big_int& operator+=(T&& rhs) requires _common-big-int-type-with_<T, basic_big_int>;
template <class T> constexpr basic_big_int& operator-=(T&& rhs) requires _common-big-int-type-with_<T, basic_big_int>;
template <class T> constexpr basic_big_int& operator*=(T&& rhs) requires _common-big-int-type-with_<T, basic_big_int>;
template <class T> constexpr basic_big_int& operator/=(T&& rhs) requires _common-big-int-type-with_<T, basic_big_int>;
template <class T> constexpr basic_big_int& operator%=(T&& rhs) requires _common-big-int-type-with_<T, basic_big_int>;
template <class T> constexpr basic_big_int& operator&=(T&& rhs) requires _common-big-int-type-with_<T, basic_big_int>;
template <class T> constexpr basic_big_int& operator|=(T&& rhs) requires _common-big-int-type-with_<T, basic_big_int>;
template <class T> constexpr basic_big_int& operator^=(T&& rhs) requires _common-big-int-type-with_<T, basic_big_int>;
template <_signed-or-unsigned_ S> constexpr basic_big_int& operator<<=(S s);
template <_signed-or-unsigned_ S> constexpr basic_big_int& operator>>=(S s);
// [big.int.modifiers], swap
constexpr void swap(basic_big_int& x) noexcept(
std::allocator_traits<allocator_type>::propagate_on_container_swap::value ||
std::allocator_traits<allocator_type>::is_always_equal::value);
// [big.int.ops], observers
constexpr std::span<const uint_multiprecision_t> representation() const noexcept;
constexpr size_type representation_size() const noexcept;
constexpr allocator_type get_allocator() const noexcept;
constexpr size_type size() const noexcept;
constexpr size_type max_size() const noexcept;
constexpr size_type max_representation_size() const noexcept;
constexpr void reserve(size_type n);
constexpr void reserve_representation(size_type n);
constexpr size_type capacity() const noexcept;
constexpr size_type representation_capacity() const noexcept;
constexpr void shrink_to_fit();
// [big.int.unary], unary operators
constexpr basic_big_int operator+() const&;
constexpr basic_big_int operator+() && noexcept;
constexpr basic_big_int operator-() const&;
constexpr basic_big_int operator-() && noexcept;
constexpr basic_big_int operator~() const&;
constexpr basic_big_int operator~() &&;
constexpr basic_big_int& operator++();
constexpr basic_big_int operator++(int);
constexpr basic_big_int& operator--();
constexpr basic_big_int operator--(int);
// [big.int.conv], conversion
template <_cv-unqualified-arithmetic_ T>
constexpr explicit operator T() const noexcept;
};
// [big.int.special], specialized algorithms
template <std::size_t b, class L, class A>
constexpr void swap(basic_big_int<b, L, A>& x, basic_big_int<b, L, A>& y)
noexcept(noexcept(x.swap(y)));
// [big.int.cmp], comparison operators
template <class L, _common-big-int-type-with_<L> R>
constexpr bool operator==(const L& lhs, const R& rhs) noexcept;
template <class L, _common-big-int-type-with_<L> R>
constexpr std::strong_ordering operator<=>(const L& lhs, const R& rhs) noexcept;
// [big.int.binary], binary operators
template <class L, class R> constexpr _common-big-int-type_<L, R> operator+(L&& x, R&& y);
template <class L, class R> constexpr _common-big-int-type_<L, R> operator-(L&& x, R&& y);
template <class L, class R> constexpr _common-big-int-type_<L, R> operator*(L&& x, R&& y);
template <class L, class R> constexpr _common-big-int-type_<L, R> operator/(L&& x, R&& y);
template <class L, class R> constexpr _common-big-int-type_<L, R> operator%(L&& x, R&& y);
template <class L, class R> constexpr _common-big-int-type_<L, R> operator&(L&& x, R&& y);
template <class L, class R> constexpr _common-big-int-type_<L, R> operator|(L&& x, R&& y);
template <class L, class R> constexpr _common-big-int-type_<L, R> operator^(L&& x, R&& y);
template <class T, _signed-or-unsigned_ S>
requires /* remove_cvref_t<T> is a basic_big_int specialization */
constexpr std::remove_cvref_t<T> operator<<(T&& x, S s);
template <class T, _signed-or-unsigned_ S>
requires /* remove_cvref_t<T> is a basic_big_int specialization */
constexpr std::remove_cvref_t<T> operator>>(T&& x, S s);
// [big.int.hash], hash support
template <std::size_t b, class L, class A>
struct std::hash<beman::big_int::basic_big_int<b, L, A>>;
Member types and constants
| Name | Description |
|---|---|
|
The allocator type |
|
|
|
|
|
|
|
The number of |
|
|
Constructors and destructor
Default constructor
constexpr basic_big_int() noexcept(noexcept(Allocator()));
Initializes the integer value to zero and default-constructs the allocator.
-
Preconditions: None.
-
Postconditions: None.
Allocator constructor
constexpr explicit basic_big_int(const Allocator& a) noexcept;
Initializes the integer value to zero and initializes the allocator to a.
-
Preconditions: None.
-
Postconditions: None.
Copy constructor
constexpr basic_big_int(const basic_big_int& x);
Initializes the integer value to that of x and initializes the allocator to
allocator_traits<Allocator>::select_on_container_copy_construction(x.get_allocator()).
-
Preconditions: None.
-
Postconditions: None.
-
Throws: Nothing if the effective width of the integer value of
xis less than or equal toinplace_bits; otherwise, any exception thrown during allocation.
Move constructor
constexpr basic_big_int(basic_big_int&& x) noexcept;
Initializes the integer value to that of x and initializes the allocator by
moving from x.get_allocator().
-
Preconditions: None.
-
Postconditions:
xis left in a valid state with an unspecified value.
Allocator-extended copy constructor
constexpr basic_big_int(const basic_big_int& x, const std::type_identity_t<Allocator>& a);
Initializes the integer value to that of x and initializes the allocator to
a. Unlike the copy constructor, select_on_container_copy_construction is
not consulted, so this is how a copy is placed on a chosen allocator, such as
the source’s own: basic_big_int y(x, x.get_allocator());.
-
Preconditions: None.
-
Postconditions: None.
-
Throws: Nothing if the effective width of the integer value of
xis less than or equal toinplace_bits; otherwise, any exception thrown during allocation.
Allocator-extended move constructor
constexpr basic_big_int(basic_big_int&& x, const std::type_identity_t<Allocator>& a);
Initializes the integer value to that of x and initializes the allocator to
a. If allocator_traits<Allocator>::is_always_equal::value is true or
a == x.get_allocator(), the storage of x is taken over as by the move
constructor. Otherwise a cannot deallocate that storage, so the value is
copied into storage obtained from a, as by the allocator-extended copy
constructor.
-
Preconditions: None.
-
Postconditions:
xis left in a valid state with an unspecified value. -
Throws: Nothing if the storage of
xis taken over or the effective width of the integer value ofxis less than or equal toinplace_bits; otherwise, any exception thrown during allocation. If an exception is thrown,xis unchanged.
Uses-allocator construction calls these two constructors, so an allocator-aware
container such as std::pmr::vector copies or moves each basic_big_int element onto
its own allocator. When the element already uses that allocator, as it does when the
container reallocates, the move takes over the element’s storage instead of allocating.
Because the allocator parameter is a non-deduced context, class template argument
deduction takes the type from x alone, and a may be anything convertible to
Allocator: basic_big_int y(x, &resource) deduces the same pmr::basic_big_int
as x.
|
Converting constructor from an arithmetic type
template <_arbitrary-arithmetic_ T>
requires(!std::same_as<std::remove_cvref_t<T>, basic_big_int>)
constexpr explicit(/* see below */) basic_big_int(T&& value) noexcept(/* see below */);
Initializes the integer value from value. If std::remove_cvref_t<T> is an
integral type or a basic_big_int specialization, the integer value is that of
value. If it is a floating-point type, the integer value is that obtained by
discarding the fractional part of value (truncation toward zero).
-
Constraints:
std::remove_cvref_t<T>is notbasic_big_int, and is a built-in integer type, a floating-point type, or abasic_big_intspecialization. -
Preconditions: If
std::remove_cvref_t<T>is a floating-point type,valueis finite. -
Postconditions: None.
-
Throws: Nothing if the effective width of the resulting value is less than or equal to
inplace_bits; otherwise, any exception thrown during allocation. -
Remarks: The constructor is
explicitunlessstd::remove_cvref_t<T>is a built-in signed or unsigned integer type or the samebasic_big_intspecialization. The expression insidenoexceptistruewhenvalueis an integral type whose width does not exceedinplace_bits.
Converting constructor from an arithmetic type with an allocator
template <_arbitrary-arithmetic_ T>
constexpr basic_big_int(const T& value, const allocator_type& a) noexcept(/* see below */);
Equivalent to the converting constructor above, but initializes the allocator to
a.
-
Preconditions: If
std::remove_cvref_t<T>is a floating-point type,valueis finite. -
Postconditions: None.
-
Throws: Nothing if the effective width of the resulting value is less than or equal to
inplace_bits; otherwise, any exception thrown during allocation.
Iterator-pair constructor
template <std::input_iterator I, std::sentinel_for<I> S>
requires _signed-or-unsigned_<std::iter_value_t<I>>
constexpr basic_big_int(I begin, S end, const allocator_type& a = allocator_type());
Initializes the integer value by concatenating the base-2 representation of each
element in [begin, end), where the first element holds the least significant
limb. If the iterator’s value type is signed, the combined representation is
interpreted as a signed integer; otherwise it is interpreted as an unsigned
integer. Initializes the allocator to a.
-
Preconditions:
[begin, end)is a valid range. -
Postconditions: None.
from_range constructor
template <std::ranges::input_range R>
requires _signed-or-unsigned_<std::ranges::range_value_t<R>>
constexpr basic_big_int(std::from_range_t, R&& r, const allocator_type& a = allocator_type());
Initializes the integer value by concatenating the base-2 representation of each
element in r, where the first element holds the least significant limb. If the
range’s value type is signed, the combined representation is interpreted as a
signed integer; otherwise it is interpreted as an unsigned integer. Initializes
the allocator to a.
-
Preconditions: None.
-
Postconditions: None.
This constructor is available when the standard library provides
<ranges> container support (the __cpp_lib_containers_ranges feature-test macro).
|
Assignment
Copy assignment
constexpr basic_big_int& operator=(const basic_big_int& x);
Sets the integer value to that of x and returns *this.
-
Preconditions: None.
-
Postconditions: None.
Move assignment
constexpr basic_big_int& operator=(basic_big_int&& x) noexcept;
Sets the integer value to that of x and returns *this.
-
Preconditions: None.
-
Postconditions:
xis left in a valid state with an unspecified value.
Assignment from an integer type
template <_arbitrary-integer_ T>
requires(!std::same_as<std::remove_cvref_t<T>, basic_big_int>)
constexpr basic_big_int& operator=(T&& x) noexcept(/* see below */);
Sets the integer value to that of x and returns *this. Note that, unlike the
converting constructor, this operator does not accept floating-point operands.
-
Constraints:
std::remove_cvref_t<T>is notbasic_big_int, and is a built-in integer type or abasic_big_intspecialization. -
Preconditions: None.
-
Postconditions: None.
-
Remarks: The expression inside
noexceptistruewhenxis an integral type whose width does not exceedinplace_bits.
Compound assignment operators
Arithmetic and bitwise compound assignment
template <class T> constexpr basic_big_int& operator+=(T&& rhs) requires _common-big-int-type-with_<T, basic_big_int>;
template <class T> constexpr basic_big_int& operator-=(T&& rhs) requires _common-big-int-type-with_<T, basic_big_int>;
template <class T> constexpr basic_big_int& operator*=(T&& rhs) requires _common-big-int-type-with_<T, basic_big_int>;
template <class T> constexpr basic_big_int& operator&=(T&& rhs) requires _common-big-int-type-with_<T, basic_big_int>;
template <class T> constexpr basic_big_int& operator|=(T&& rhs) requires _common-big-int-type-with_<T, basic_big_int>;
template <class T> constexpr basic_big_int& operator^=(T&& rhs) requires _common-big-int-type-with_<T, basic_big_int>;
Each operator @= is equivalent to *this = std::move(*this) @ std::forward<T>(rhs)
and returns *this, where @ is the corresponding binary operator.
-
Preconditions: None.
-
Postconditions: None.
-
Throws:
std::length_errorif the result would occupy more thanmax_size()bits; otherwise, any exception thrown during allocation.
Division and remainder compound assignment
template <class T> constexpr basic_big_int& operator/=(T&& rhs) requires _common-big-int-type-with_<T, basic_big_int>;
template <class T> constexpr basic_big_int& operator%=(T&& rhs) requires _common-big-int-type-with_<T, basic_big_int>;
operator/= sets *this to the quotient and operator%= sets *this to the
remainder of dividing *this by rhs, truncating toward zero, and returns
*this.
-
Preconditions: The integer value of
rhsis nonzero. -
Postconditions: None.
Shift compound assignment
template <_signed-or-unsigned_ S> constexpr basic_big_int& operator<<=(S s);
template <_signed-or-unsigned_ S> constexpr basic_big_int& operator>>=(S s);
operator<⇐ is equivalent to *this = std::move(*this) << s (multiplication by
2^s); operator>>= is equivalent to *this = std::move(*this) >> s (division
by 2^s, rounded toward negative infinity). Each returns *this.
-
Preconditions:
s >= 0, andsdoes not exceed the maximum shiftmax_size(). -
Postconditions: None.
-
Throws:
std::length_errorif the result would occupy more thanmax_size()bits; otherwise, any exception thrown during allocation.
Swap
Member swap
constexpr void swap(basic_big_int& x) noexcept(
std::allocator_traits<allocator_type>::propagate_on_container_swap::value ||
std::allocator_traits<allocator_type>::is_always_equal::value);
Exchanges the integer values of *this and x. Any dynamically-allocated
storage is exchanged directly rather than reallocated, so the operation neither
allocates nor deallocates. If
std::allocator_traits<allocator_type>::propagate_on_container_swap::value is
true, the allocators are also exchanged using an unqualified call to swap;
otherwise they are left unchanged.
-
Preconditions:
std::allocator_traits<allocator_type>::propagate_on_container_swap::valueistrue, orget_allocator() == x.get_allocator(). -
Postconditions:
*thisholds the integer value thatxheld before the call, andxholds the integer value that*thisheld before the call. The allocators are exchanged if and only ifstd::allocator_traits<allocator_type>::propagate_on_container_swap::valueistrue. -
Throws: Nothing.
Non-member swap
template <std::size_t b, class L, class A>
constexpr void swap(basic_big_int<b, L, A>& x, basic_big_int<b, L, A>& y)
noexcept(noexcept(x.swap(y)));
Equivalent to x.swap(y), and so subject to the same preconditions. It lives in
the beman::big_int namespace and is found by argument-dependent lookup, which
makes it the overload selected both by an unqualified swap(x, y) and by the
using std::swap; swap(x, y) idiom that generic code and the standard
algorithms use. Selecting it in preference to std::swap matters: std::swap
would exchange the two values with three moves, and would not honour
std::allocator_traits<A>::propagate_on_container_swap.
Both arguments must name the same basic_big_int specialization; there is no
overload that exchanges values across differing min_inplace_bits, limb types,
or allocators.
-
Effects: As if by
x.swap(y). -
Preconditions:
std::allocator_traits<A>::propagate_on_container_swap::valueistrue, orx.get_allocator() == y.get_allocator(). -
Postconditions:
xholds the integer value thatyheld before the call, andyholds the integer value thatxheld before the call. -
Throws: Nothing.
Observers
Throughout this section, a "limb" is one uint_multiprecision_t object of the
magnitude’s little-endian representation.
representation
constexpr std::span<const uint_multiprecision_t> representation() const noexcept;
Returns a std::span over the limbs that make up the magnitude, whether stored
in place or in dynamic storage. The first limb in the range holds the least
significant bits.
-
Preconditions: None.
-
Postconditions: None.
-
Remarks: If the integer value is greater than or equal to zero,
basic_big_int(std::from_range, representation(), get_allocator())has the same integer value.
representation_size
constexpr size_type representation_size() const noexcept;
Returns the number of limbs that make up the magnitude: 1 if the integer value
is zero, and otherwise ceil(size() / std::numeric_limits<uint_multiprecision_t>::digits).
This is the size of the span returned by representation().
-
Preconditions: None.
-
Postconditions: None.
get_allocator
constexpr allocator_type get_allocator() const noexcept;
Returns a copy of the object’s allocator.
-
Preconditions: None.
-
Postconditions: None.
size
constexpr size_type size() const noexcept;
Returns the number of bits required to represent the magnitude of the value:
0 if the integer value is zero, and otherwise floor(log2(|v|)) + 1, where
v is the integer value.
-
Preconditions: None.
-
Postconditions: None.
max_size
constexpr size_type max_size() const noexcept;
Returns the maximum number of bits that the magnitude may occupy, that is,
max_representation_size() * std::numeric_limits<uint_multiprecision_t>::digits.
An operation that would grow the magnitude beyond this many bits throws
std::length_error; see the length limit.
This is a ceiling on the representation, not a largest value of the type, and it
lies far beyond any allocation a program can make. std::numeric_limits
accordingly reports basic_big_int as unbounded; see
Why is_bounded is false.
-
Preconditions: None.
-
Postconditions: None.
-
Throws: Nothing.
max_representation_size
constexpr size_type max_representation_size() const noexcept;
Returns the maximum number of limbs that can be part of the representation, that
is, max_size() / std::numeric_limits<uint_multiprecision_t>::digits.
-
Preconditions: None.
-
Postconditions: None.
-
Throws: Nothing.
reserve
constexpr void reserve(size_type n);
A directive, expressed as a number of bits, that informs the object of a planned
increase in size so that storage can be allocated up front. The bit count is
rounded up to a whole number of limbs; equivalent to
reserve_representation(ceil(n / std::numeric_limits<uint_multiprecision_t>::digits)).
-
Preconditions: None.
-
Postconditions: If reallocation occurs,
capacity()is greater than or equal ton; otherwise,capacity()is unchanged. -
Throws:
std::length_errorifnis greater thanmax_size(); otherwise, any exception thrown during allocation. In either case*thisis unchanged.
reserve_representation
constexpr void reserve_representation(size_type n);
A directive that informs the object of a planned increase in size, expressed as a
number of limbs, so that storage can be allocated up front. Equivalent to
reserve(n * std::numeric_limits<uint_multiprecision_t>::digits).
-
Preconditions: None.
-
Postconditions: If reallocation occurs,
representation_capacity()is greater than or equal ton; otherwise, the capacity is unchanged. -
Throws:
std::length_errorifnis greater thanmax_representation_size(); otherwise, any exception thrown during allocation. In either case*thisis unchanged.
capacity
constexpr size_type capacity() const noexcept;
Returns the number of bits of storage currently available without reallocating,
that is, representation_capacity() * std::numeric_limits<uint_multiprecision_t>::digits.
While the value is held in the in-place (small-object) storage this equals
inplace_bits.
-
Preconditions: None.
-
Postconditions: None.
representation_capacity
constexpr size_type representation_capacity() const noexcept;
Returns the number of limbs that can be held without allocating: the greater of
inplace_capacity and the number of limbs of dynamically-allocated storage
currently available. Unlike capacity(), which is expressed in bits, this is a
limb count that never reports fewer than inplace_capacity limbs.
-
Preconditions: None.
-
Postconditions: None.
shrink_to_fit
constexpr void shrink_to_fit();
If the magnitude fits in the in-place storage, frees any dynamic allocation and
moves the value into the in-place storage. Otherwise, this is a non-binding
request to reduce the dynamic capacity to the number of limbs in use; it never
increases capacity(), but may reduce it, which can cause a reallocation.
-
Preconditions: None.
-
Postconditions: None.
Unary operators
The const& overloads build their result by copy construction, so its
allocator is
allocator_traits<Allocator>::select_on_container_copy_construction(get_allocator()),
as abs’s is. The `&& overloads take over the operand’s storage and so carry
the operand’s own allocator.
operator+
constexpr basic_big_int operator+() const&;
constexpr basic_big_int operator+() && noexcept;
Equivalent to return *this; for the lvalue overload and
return std::move(*this); for the rvalue overload, so each selects the
corresponding constructor and the allocator behavior above follows from it. The
value is unchanged.
-
Preconditions: None.
-
Postconditions: None.
operator-
constexpr basic_big_int operator-() const&;
constexpr basic_big_int operator-() && noexcept;
Returns the arithmetic negation of the value (equivalent to 0 - *this). The
rvalue overload reuses the operand’s storage and only flips the sign.
-
Preconditions: None.
-
Postconditions: None.
operator~
constexpr basic_big_int operator~() const&;
constexpr basic_big_int operator~() &&;
Returns the bitwise complement of the value as if performed on an
infinite-width two’s-complement representation (equivalent to -1 - *this).
-
Preconditions: None.
-
Postconditions: None.
operator++
constexpr basic_big_int& operator++(); // pre-increment
constexpr basic_big_int operator++(int); // post-increment
Pre-increment is equivalent to *this += 1 and returns *this. Post-increment
increments *this and returns a copy of the value prior to the increment.
Neither is ref-qualified, so both apply to an rvalue.
-
Preconditions: None.
-
Postconditions: None.
operator--
constexpr basic_big_int& operator--(); // pre-decrement
constexpr basic_big_int operator--(int); // post-decrement
Pre-decrement is equivalent to *this -= 1 and returns *this. Post-decrement
decrements *this and returns a copy of the value prior to the decrement.
Neither is ref-qualified, so both apply to an rvalue.
-
Preconditions: None.
-
Postconditions: None.
Conversion operator
template <_cv-unqualified-arithmetic_ T>
constexpr explicit operator T() const noexcept;
Converts the value to T. The result is static_cast<T>(static_cast<U>(v)),
where v is the integer value and U is a hypothetical signed integer type
wide enough to represent v. If T is bool, the result is true when the
value is nonzero and false otherwise. If T is a floating-point type, the
result is determined as if by a floating-integral conversion.
-
Constraints:
Tis a cv-unqualified arithmetic type. -
Preconditions: None.
-
Postconditions: None.
Comparison operators
template <class L, _common-big-int-type-with_<L> R>
constexpr bool operator==(const L& lhs, const R& rhs) noexcept;
template <class L, _common-big-int-type-with_<L> R>
constexpr std::strong_ordering operator<=>(const L& lhs, const R& rhs) noexcept;
operator== returns true if the integer value of lhs equals the integer
value of rhs. operator<⇒ returns std::strong_ordering::less,
std::strong_ordering::greater, or std::strong_ordering::equal according to
whether the integer value of lhs is less than, greater than, or equal to the
integer value of rhs. At least one operand is a basic_big_int; the other may
be the same basic_big_int specialization or a built-in signed or unsigned
integer type.
-
Preconditions: None.
-
Postconditions: None.
Binary operators
Every operator in this section returns a new value, so its allocator is the
basic_big_int operand’s allocator run through
allocator_traits<Allocator>::select_on_container_copy_construction
Arithmetic and bitwise operators
template <class L, class R> constexpr _common-big-int-type_<L, R> operator+(L&& x, R&& y);
template <class L, class R> constexpr _common-big-int-type_<L, R> operator-(L&& x, R&& y);
template <class L, class R> constexpr _common-big-int-type_<L, R> operator*(L&& x, R&& y);
template <class L, class R> constexpr _common-big-int-type_<L, R> operator&(L&& x, R&& y);
template <class L, class R> constexpr _common-big-int-type_<L, R> operator|(L&& x, R&& y);
template <class L, class R> constexpr _common-big-int-type_<L, R> operator^(L&& x, R&& y);
Each returns a basic_big_int whose integer value is T(x) @ T(y), where @
is the corresponding operator and T is a hypothetical signed integer type with
infinite range. One operand is a basic_big_int and the other is the same
specialization or a built-in signed or unsigned integer type.
-
Preconditions: None.
-
Postconditions: None.
-
Throws:
std::length_errorif the result would occupy more thanmax_size()bits; otherwise, any exception thrown during allocation. -
Remarks: The bitwise operators (
&,|,^) behave as if performed on a two’s-complement representation in which non-negative numbers have infinitely many leading zero bits and negative numbers have infinitely many leading one bits.
Division and remainder operators
template <class L, class R> constexpr _common-big-int-type_<L, R> operator/(L&& x, R&& y);
template <class L, class R> constexpr _common-big-int-type_<L, R> operator%(L&& x, R&& y);
operator/ returns the quotient and operator% returns the remainder of
dividing x by y, truncating toward zero (matching the behavior of the
built-in integer types).
-
Preconditions: The integer value of
yis nonzero. -
Postconditions: None.
Shift operators
template <class T, _signed-or-unsigned_ S>
requires /* remove_cvref_t<T> is a basic_big_int specialization */
constexpr std::remove_cvref_t<T> operator<<(T&& x, S s);
template <class T, _signed-or-unsigned_ S>
requires /* remove_cvref_t<T> is a basic_big_int specialization */
constexpr std::remove_cvref_t<T> operator>>(T&& x, S s);
operator<< returns a basic_big_int whose integer value is v * 2^s, and
operator>> returns one whose integer value is v / 2^s rounded toward
negative infinity, where v is the integer value of x. The result’s allocator
is selected as described for the binary operators above.
-
Constraints:
std::remove_cvref_t<T>is abasic_big_intspecialization. -
Preconditions:
s >= 0, andsdoes not exceed the maximum shift for the result type (max_size()). -
Postconditions: None.
-
Throws:
std::length_errorif the result would occupy more thanmax_size()bits; otherwise, any exception thrown during allocation.
Hash support
template <std::size_t b, class L, class A>
struct std::hash<beman::big_int::basic_big_int<b, L, A>> {
std::size_t operator()(const beman::big_int::basic_big_int<b, L, A>& x) const noexcept;
};
Returns a hash of the integer value of x.
The digest is a function of that value and of nothing else. Equal values hash
equally whatever their min_inplace_bits, Limb, and Allocator arguments, and
whatever the width of
uint_multiprecision_t on the
target: a value hashes the same where limbs are 32 bits wide as it does where
they are 64 bits wide.
Where a signed bit-precise integer type can represent x and the implementation gives
that type a digest, the digest is
std::hash<_BitInt(N)>()(static_cast<_BitInt(N)>(x)) for the narrowest
such N, so a basic_big_int key and a _BitInt(N) key of the same value hash
alike and can be hashed interchangeably.
The widths tried are one per object size: a _BitInt(N) past one machine word
occupies ceil(N / word_bits) words, and on an implementation that hashes a scalar by
its object representation every _BitInt of a given size has one digest, so a
rung at each size covers every width that shares that size. A value hashed on the
192-bit rung therefore also matches a _BitInt(160) key of the same value, and
one hashed on the narrowest rung matches every _BitInt no wider than a word.
The ladder runs from one word up to the widest object the implementation’s own
std::hash covers, which on a 64-bit target with libc is four words, so 64, 128,
192, and 256 bits; a rung the target cannot form, or whose `std::hash` the
implementation does not provide, is passed over. The reach is the
`BEMAN_BIG_INT_HASH_MAX_OBJECT_WORDS` macro, counted in `std::size_t` words and
defaulting to four, because libc hashes a scalar past that size with a helper it only
declares, which is a hard error rather than something a concept can reject. Defining
that macro to a larger value is the whole change needed to hash on wider rungs once a
standard library covers them.
Where no rung is available every value takes the fallback below; that is the case on an
implementation that hashes no _BitInt, and on one whose compiler has no
_BitInt at all.
An implementation that hashes a scalar by its object representation gives
_BitInt(64) and std::int64_t the same digest, since they share one. On such
an implementation, where a machine word is 64 bits, a value in the range of
std::int64_t therefore also hashes as
std::hash<std::int64_t>()(static_cast<std::int64_t>(x)).
Beyond the widest available width the digest is not itself part of the interface and may change
between releases. It is currently SipHash-2-4 over the little-endian byte string of
the magnitude, trimmed of its most significant zero bytes. That string is
size() bits rounded up to a whole number of bytes. A negative value is
distinguished from its magnitude before the string is absorbed. Where std::size_t
is narrower than 64 bits, the digest is folded down to its width.
-
Preconditions: None.
-
Postconditions: None.