std::big_int

Document number:
D4444R0
Date:
2026-04-16
Audience:
SG6
Project:
ISO/IEC 14882 Programming Languages — C++, ISO/IEC JTC1/SC22/WG21
Reply-to:
Jan Schultke <janschultke@gmail.com>
GitHub Issue:
wg21.link/P4444/github
Source:
github.com/eisenwave/cpp-proposals/blob/master/src/big-int.cow

This paper is a technical specification of std::big_int. Other sections will be filled in over time.

Contents

1

Design

2

Wording

2.1

[version.syn]

2.2

[big.int]

2.3

[charconv]

2.4

[numeric.int.div]

2.5

[numeric.abs]

2.6

[numeric.sat.cast]

2.7

[numeric.conversions]

3

References

1. Design

Open design questions:

2. Wording

The changes are relative to [N5032].

[version.syn]

Add a feature-test macro to [version.syn] as follows:

#define __cpp_lib_big_int 20XXXXL // freestanding, also in <big_int>

[big.int]

In Clause [numerics], insert a new subclause immediately following [complex.numbers].

X Arbitrary-precision arithmetic [big.int]

X.1 General [big.int.general]

1 The header <big_int> defines a class template for performing arbitrary-precision integer arithmetic, as well as related type aliases.

X.2 Header <big_int> synopsis [big.int.syn]

#include <compare> #include <span> namespace std { // alias uint_multiprecision_t using uint_multiprecision_t = see below; // [big.int.class], class template basic_big_int template<size_t min_inplace_capacity, class Allocator = allocator<uint_multiprecision_t>> class basic_big_int; // [big.int.expos], exposition-only helpers template<class T> concept signed-or-unsigned = see below; // exposition only template<class T> concept arbitrary-integer = see below; // exposition only template<class T> concept arbitrary-arithmetic-type = see below; // exposition only template<class L, class R> using common-big-int-type = see below; // exposition only template<class T, class U> concept common-big-int-type-with = requires { // exposition only typename common-big-int-type<T, U>; }; // [big.int.alias], alias big_int using big_int = basic_big_int<see below>; // [big.int.cmp], non-member comparison operator functions 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 strong_ordering operator<=>(const L& lhs, const R& rhs) noexcept; // [big.int.binary], binary operations 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> remove_cvref_t<T> operator<<(T&& x, S s); template<class T, signed-or-unsigned S> remove_cvref_t<T> operator>>(T&& x, S s); namespace pmr { template<size_t b> using basic_big_int = std::basic_big_int<b, polymorphic_allocator<uint_multiprecision_t>>; using big_int = basic_big_int<std::big_int::inplace_bits>; } // [big.int.hash], hash support template<class T> struct hash; template<size_t b, class A> struct hash<basic_big_int<b, A>>; // [big.int.literal], literals inline namespace literals { inline namespace big_int_literals { template<char... digits> constexpr big_int operator""n() noexcept(see below); } } }

1 The type alias uint_multiprecision_t denotes a standard unsigned or extended unsigned integer type ([basic.fundamental]) which has no padding bits.

2 Recommended practice: uint_multiprecision_t should be chosen to have the greatest possible width so that an arithmetic expression ([expr.pre]) performed on operands of the type corresponds to single instruction in the execution environment.

It is important not to overconstrain here because there are many edge cases in architectures:

  • 8-bit microcontrollers only have 8-bit arithmetic but can address memory with 16-bit addressing; the appropriate type there is presumably uint8_t despite size_t and int being wider.
  • WASM32 only has 32-bit addressable memory and has a 32-bit size_t; however, WASM32 supports 64-bit arithmetic instructions, so the appropriate limb type there is uint64_t.
  • In most cases, the limb type can simply be size_t.

X.3 Class template basic_big_int [big.int.class]

template<size_t min_inplace_capacity, class Allocator> class basic_big_int { // [big.int.defns], types and constants using allocator_type = Allocator; using size_type = implementation-defined; static constexpr size_type inplace_representation_capacity = see below; static constexpr size_type inplace_capacity = inplace_representation_capacity * numeric_limits<uint_multiprecision_t>::digits; // [big.int.expos], exposition-only helpers template<class T> inline constexpr bool no-alloc-constructible-from = see below; // exposition only // [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; template<arbitrary-arithmetic-type T> constexpr explicit(see below) basic_big_int(T&& x) noexcept(no-alloc-constructible-from<T>); template<arbitrary-arithmetic-type T> constexpr explicit basic_big_int(const T& x, const Allocator& a) noexcept(no-alloc-constructible-from<T>); template<input_range R> requires signed-or-unsigned<ranges::range_value_t<R>> constexpr explicit basic_big_int(from_range_t, R&&, const Allocator& a = Allocator()); constexpr ~basic_big_int(); // [big.int.ops], operations constexpr span<const uint_multiprecision_t> representation() const noexcept; constexpr size_type size() const noexcept; constexpr size_type representation_size() const noexcept; constexpr size_type max_size() const noexcept; constexpr size_type max_representation_size() const noexcept; constexpr size_type capacity() const noexcept; constexpr size_type representation_capacity() const noexcept; constexpr allocator_type get_allocator() const noexcept; constexpr void reserve(size_type n); constexpr void reserve_representation(size_type n); constexpr void shrink_to_fit(); // [big.int.modifiers], modifiers constexpr basic_big_int& operator=(const basic_big_int& x); constexpr basic_big_int& operator=(basic_big_int&& x) noexcept; template<arbitrary-integer T> constexpr basic_big_int& operator=(T&& x) noexcept(no-alloc-constructible-from<T>); template<common-big-int-type-with<basic_big_int> T> constexpr basic_big_int& operator+=(T&& x); template<common-big-int-type-with<basic_big_int> T> constexpr basic_big_int& operator-=(T&& x); template<common-big-int-type-with<basic_big_int> T> constexpr basic_big_int& operator*=(T&& x); template<common-big-int-type-with<basic_big_int> T> constexpr basic_big_int& operator/=(T&& x); template<common-big-int-type-with<basic_big_int> T> constexpr basic_big_int& operator%=(T&& x); template<common-big-int-type-with<basic_big_int> T> constexpr basic_big_int& operator&=(T&& x); template<common-big-int-type-with<basic_big_int> T> constexpr basic_big_int& operator|=(T&& x); template<common-big-int-type-with<basic_big_int> T> constexpr basic_big_int& operator^=(T&& x); template<signed-or-unsigned S> constexpr basic_big_int& operator<<=(S s); template<signed-or-unsigned S> constexpr basic_big_int& operator>>=(S s); constexpr void swap(basic_big_int& x) noexcept(allocator_traits<Allocator>::propagate_on_container_swap::value || allocator_traits<Allocator>::is_always_equal::value); // [big.int.conv], conversions template<class T> constexpr explicit operator T() const noexcept; // [big.int.unary], unary operations 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) &; };

1 A basic_big_int represents an integer value; the integer value of an object of integral type is the value ([basic.types.general]) of that object. The magnitude of the integer value of a basic_big_int is either represented using subobjects nested within a basic_big_int or represented within storage obtained from the given Allocator; the sign of the integer value is represented separately.

2 The effective width of an integer value x is the width of the smallest hypothetical unsigned integer type ([basic.fundamental]) able to represent the magnitude of x.

3 Template parameter min_inplace_capacity specifies the minimum width of integers that a basic_big_int shall be capable of representing using a subobject nested within. min_inplace_capacity shall be nonzero and less than or equal to an implementation-defined limit. That limit shall be greater than or equal to the maximum width of any standard or extended integer type ([basic.fundamental]).

The design aspiration is that basic_big_int is capable of storing any standard or extended integer type without the use of allocations, including __int128 and unsigned __int128. This is easily possible by just putting the proper integer type into a union with the pointer to allocated data.

It is theoretically possible to use a greater limit and store a uint_multiprecision_t[] within the basic_big_int, but this is more difficult to implement than if you only need small object optimizations for a single scalar, so the initial proposal maybe shouldn't mandate it.

4 During constant evaluation, if the effective width of the integer value of a basic_big_int is less than or equal to its inplace_bits member, the object shall not hold an allocation following any operation.
[Note: The behavior is as if shrink_to_fit() was called following every operation that may allocate. — end note]

This ensures that even without non-transient allocations, as long as the value can be stored directly in the object, you can create a constexpr big_int variable. Otherwise, it would be possible to hold e.g. the value 123 in dynamic storage, even though it can be represented without allocations.

Giving the implementation the freedom to hold an unnecessary allocation still makes sense because that allocation may be reused at a later point.

5 The representation of a basic_big_int object is the sequence of uint_multiprecision_t elements that collectively represents the integer value of that object. Unless otherwise stated,

described in [big.int] invalidates the representation of the object, meaning that results previously returned by representation() are no longer valid.

X.3.1 Types and constants [big.int.defns]

static constexpr size_type inplace_representation_capacity = see below;

1 The value of the static data member inplace_representation_capacity is the amount of uint_multiprecision_t nested within a basic_big_int object and which participate in representing its integer value.

2 Remarks: The value of inplace_representation_capacity shall be at least div_to_pos_inf(min_inplace_capacity, numeric_limits<uint_multiprecision_t>::digits).

X.3.2 Exposition-only helpers [big.int.expos]

template<class T> concept signed-or-unsigned = see below;

1 The exposition-only concept signed-or-unsigned is satisfied and modeled if and only if T is a signed or unsigned integer type ([basic.fundamental]).

template<class T> concept arbitrary-integer = see below;

2 The exposition-only concept arbitrary-integer is satisfied and modeled if and only if remove_cvref_t<T> is either a signed or unsigned integer type ([basic.fundamental]) or a specialization of basic_big_int.

template<class T> concept arbitrary-arithmetic-type = see below;

3 The exposition-only concept arbitrary-arithmetic-type is satisfied and modeled if and only if remove_cvref_t<T> is either a cv-unqualified arithmetic type ([basic.fundamental]) or a specialization of basic_big_int.

template<class L, class R> using common-big-int-type = see below;

4 Let LT be remove_cvref_t<L>, and let RT be remove_cvref_t<R>.

5 Result:

  • If LT and RT are the same specialization of basic_big_int, LT;
  • otherwise, if LT is a specialization of basic_big_int and RT is a signed or unsigned integer type ([basic.fundamental]), LT;
  • otherwise, if RT is a specialization of basic_big_int and LT is a signed or unsigned integer type ([basic.fundamental]), RT;
  • otherwise, the type alias is ill-formed.
template<class T> inline constexpr bool no-alloc-constructible-from = see below;

6 Effects: no-alloc-constructible-from is true if remove_cvref_t<T> is a signed or unsigned integer type whose width is less than or equal to inplace_bits, and false otherwise.

X.3.3 Construct/copy/destroy [big.int.cons]

constexpr basic_big_int() noexcept(noexcept(Allocator()));

1 Effects: Initializes the integer value to zero.

constexpr explicit basic_big_int(const Allocator& a) noexcept;

2 Effects: Initializes the integer value to zero. Initializes the allocator to a.

constexpr basic_big_int(const basic_big_int& x);

3 Effects: Initializes the integer value to that of x. Initializes the allocator to x.get_allocator().

4 Throws: Nothing if the effective width of the integer value of x is less than or equal to inplace_bits; otherwise, exceptions thrown during allocation.

constexpr basic_big_int(basic_big_int&& x) noexcept;

5 Effects: Initializes the integer value to that of x. Initializes the allocator to x.get_allocator().

template<arbitrary-arithmetic-type T> constexpr explicit(see below) basic_big_int(T&& x) noexcept(no-alloc-constructible-from<T>);

6 Constraints: is_same_v<basic_big_int, remove_cvref_t<T>> is false.

This constraint ensures that there is no ambiguity with the copy constructor or move constructor. Also note that we support construction from basic_big_int with other allocators.

7 Preconditions: If remove_cvref_t<T> is a floating-point type, the value of x is finite.

8 Effects: If remove_cvref_t<T> is an integral type or a specialization of basic_big_int, initializes the integer value to that of x. Otherwise, remove_cvref_t<T> is a floating-point type, and this object is initialized to the integer value obtained by discarding the fractional part of x.

9 Throws: Nothing if the effective width of the integer value this object is initialized with is less than or equal to inplace_bits; otherwise, exceptions thrown during allocation.

10 Remarks: The constructor is explicit if remove_cvref_t<T> is neither a signed or unsigned integer type ([basic.fundamental]) nor basic_big_int<inplace_bits, Allocator>.

The design goal here is to permit conversion from any arithmetic type as well as for basic_big_int specializations with other allocators, but to make allocator mixing and floating-point conversions explicit. Also explicit is the conversion from character types to basic_big_int, which is arguably needed because character types and integers are used in different domains.

template<arbitrary-arithmetic-type T> constexpr basic_big_int(const T& x, const Allocator& a) noexcept(no-alloc-constructible-from<T>);

11 Preconditions: If remove_cvref_t<T> is a floating-point type, the value of x is finite.

12 Effects: If remove_cvref_t<T> is an integral type or a specialization of basic_big_int, initializes the integer value to that of x. Otherwise, remove_cvref_t<T> is a floating-point type, and this object is initialized to the integer value obtained by discarding the fractional part of x. Initializes the allocator to a.

13 Throws: Nothing if the effective width of the integer value this object is initialized with is less than or equal to inplace_bits; otherwise, exceptions thrown during allocation.

template<input_range R> requires signed-or-unsigned<ranges::range_value_t<R>> constexpr explicit basic_big_int(from_range_t, R&& r, const Allocator& a = Allocator());

14 Effects: Initializes the integer value to an integer value formed by concatenating the base-2 representation of each element in r, where the first element in r holds the least significant part of the concatenated base-2 representation. If ranges::range_value_t<R> is a signed type, the combined base-2 representation is interpreted as that of a signed integer, otherwise as that of an unsigned integer. Initializes the allocator to a.

15 Throws: Nothing if the effective width of the combined integer value is less than or equal to inplace_bits; otherwise, exceptions thrown during allocation.

X.3.4 Operations [big.int.ops]

constexpr span<const uint_multiprecision_t> representation() const noexcept;

1 Returns: A span representing the range of digits either nested within this object or dynamically allocated, where the first digit in the range has the least significant set of bits. The size() of the result is div_to_pos_inf(representation_size(), numeric_limits<uint_multiprecision_t>::digits).

div_to_pos_inf is added by [P3724R3]. I would expect it to be available by the time big_int is standardized.

2 Remarks: If the integer value is greater or equal to zero, basic_big_int(from_range, representation(), get_allocator()) shall have the same integer value.
[Note: Consequently, elements of type uint_multiprecision_t that are part of the representation must be kept in the correct state, including otherwise extraneous upper bits of magnitude greater than the integer value. This restriction does not apply to elements that are allocated but not part of the representation. — end note]

This getter single-handedly imposes a huge amount of constraints on the implementation:

  • basic_big_int needs to store a union of dynamically allocated data and of uint_multiprecision_t to make the value accessible via span.
  • The sign bit is kept separate.
  • The padding needs to be kept zero.
constexpr size_type size() const noexcept;

3 Returns: If the integer value is zero, 0; otherwise log2 | v | + 1 , where v is the integer value.

The result is identical to std::bit_width(U(std::abs(T(v)))) for a hypothetical signed integer type T with infinite range and a hypothetical unsigned integer type U with infinite range. However, this description seems inelegant. It would also be possible to imitate the wording from [bit.pow.two], but with the addition of abs/magnitude, we are describing too complicated a math formula in prose.

4 Complexity: Constant.

constexpr size_type representation_size() const noexcept;

5 Returns: If the integer value is zero, 1; otherwise div_to_pos_inf(size(), numeric_­limits<uint_­multiprecision_t>::digits).

constexpr size_type max_size() const noexcept;

6 Returns: max_representation_size() * numeric_limits<uint_multiprecision_t>::digits.

constexpr size_type max_representation_size() const noexcept;

7 Returns: The maximum number of uint_multiprecision_t objects that can be part of the representation.

constexpr size_type capacity() const noexcept;

8 Returns: representation_capacity() * numeric_limits<uint_multiprecision_t>::digits.

constexpr size_type representation_capacity() const noexcept;

9 Returns: max(inplace_representation_capacity, dynamic-representation-capacity() * numeric_­limits<uint_­multiprecision_t>::digits), where dynamic-representation-capacity() is the number of currently allocated uint_­multiprecision_t objects.

constexpr allocator_type get_allocator() const noexcept;

10 Returns: The allocator of this object.

constexpr void reserve(size_type n);

11 Effects: A directive that informs a basic_big_int of a planned change in size, so that the storage allocation can be managed accordingly. Reallocation happens at this point if and only if the current capacity is less than the argument of reserve.

12 Postconditions: capacity() is greater or equal to the argument of reserve if reallocation happens; and equal to the previous value of capacity() otherwise.

constexpr void reserve_representation(size_type n);

13 Effects: Equivalent to: reserve(n * numeric_limits<uint_multiprecision_t>::digits);

constexpr void shrink_to_fit();

14 Effects: If the effective width of the integer value is less than or equal to inplace_bits, shrink_to_fit frees the allocation and stores the integer value within the basic_big_int object. Otherwise, shrink_to_fit is a non-binding request to reduce capacity() to size(). It does not increase capacity(), but may reduce capacity() causing reallocations.

15 Complexity: If the size is not equal to the old capacity, linear in the size of the sequence; otherwise constant.

X.3.5 Modifiers [big.int.modifiers]

constexpr basic_big_int& operator=(const basic_big_int& x);

1 Effects: Sets the integer value to that of x.

2 Returns: *this.

constexpr basic_big_int& operator=(basic_big_int&& x) noexcept;

3 Effects: Sets the integer value to that of x.

4 Returns: *this.

template<arbitrary-integer T> constexpr basic_big_int& operator=(T&& x) noexcept(no-alloc-constructible-from<T>);

5 Constraints: is_same_v<basic_big_int, remove_cvref_t<T>> is false.

6 Effects: Sets the integer value to that of x.

7 Returns: *this.

template<common-big-int-type-with<basic_big_int> T> constexpr basic_big_int& operator+=(T&& x); template<common-big-int-type-with<basic_big_int> T> constexpr basic_big_int& operator-=(T&& x); template<common-big-int-type-with<basic_big_int> T> constexpr basic_big_int& operator*=(T&& x); template<common-big-int-type-with<basic_big_int> T> constexpr basic_big_int& operator/=(T&& x); template<common-big-int-type-with<basic_big_int> T> constexpr basic_big_int& operator%=(T&& x); template<common-big-int-type-with<basic_big_int> T> constexpr basic_big_int& operator&=(T&& x); template<common-big-int-type-with<basic_big_int> T> constexpr basic_big_int& operator|=(T&& x); template<common-big-int-type-with<basic_big_int> T> constexpr basic_big_int& operator^=(T&& x);

8 Effects: Equivalent to:

*this = std::move(*this) @ std::forward<T>(x); return *this;

where @ is a placeholder for the token in the respective operator@=.

template<signed-or-unsigned S> constexpr basic_big_int& operator<<=(S s);

9 Effects: Equivalent to: return *this = std::move(*this) << s;

template<signed-or-unsigned S> constexpr basic_big_int& operator>>=(S s);

10 Effects: Equivalent to: return *this = std::move(*this) >> s;

constexpr void swap(basic_big_int& x) noexcept(allocator_traits<Allocator>::propagate_on_container_swap::value || allocator_traits<Allocator>::is_always_equal::value);

11 Effects: Exchanges the integer values of this object and of x.

What does this do for allocators?

X.3.6 Conversions [big.int.conv]

template<class T> constexpr explicit operator T() const noexcept;

1 Let U be a hypothetical signed integer type with sufficient width to represent the basic_big_int's integer value v.

2 Constraints: T is a cv-unqualified arithmetic type ([basic.fundamental]).

3 Returns: static_cast<T>(static_cast<U>(v)).
[Note: If T is bool, the result is true if v is nonzero and false otherwise ([conv.integral]). If T is a floating-point type, the result value is determined as if by floating-integral conversion ([conv.fpint]). — end note]

X.3.7 Unary operations [big.int.unary]

constexpr basic_big_int operator+() const&;

1 Effects: Equivalent to: return *this;

constexpr basic_big_int operator+() && noexcept;

2 Effects: Equivalent to: return std::move(*this);

constexpr basic_big_int operator-() const&;

3 Effects: Equivalent to: return 0 - *this;

constexpr basic_big_int operator-() && noexcept;

4 Effects: Equivalent to: return 0 - std::move(*this);

5 Complexity: Constant.

6 Remarks: The contents of the representation of the result are identical to those of representation() prior to the call.

constexpr basic_big_int operator~() const&;

7 Effects: Equivalent to: return -1 - *this;

constexpr basic_big_int operator~() &&;

8 Returns: Equivalent to: return -1 - std::move(*this);

constexpr basic_big_int& operator++() &;

9 Effects: Equivalent to: return *this += 1;

constexpr basic_big_int operator++(int) &;

10 Effects: Equivalent to:

auto copy = *this; ++(*this); return copy;
constexpr basic_big_int& operator--() &;

11 Effects: Equivalent to: return *this -= 1;

constexpr basic_big_int operator--(int) &;

12 Effects: Equivalent to:

auto copy = *this; --(*this); return copy;

X.3.8 Alias big_int [big.int.alias]

using big_int = basic_big_int<see below>;

1 Result: A specialization of basic_big_int with an implementation-defined argument B for the min_inplace_capacity constant template parameter, chosen so that B equals basic_big_int<B>::inplace_­bits.

2 Recommended practice: B should be sufficiently large so that big_int may represent the value of all commonly used integer types without allocating.

X.3.9 Non-member comparison operator functions [big.int.cmp]

template<class L, common-big-int-type-with<L> R> constexpr bool operator==(const L& x, const R& y) noexcept;

1 Returns: true if the integer value of x is equal to the integer value of y, and false otherwise.

The noexcept requirement means that it's not a valid implementation strategy to wrap any integer in basic_big_int because that may allocate. Instead, either the integer value or each limb must be compared with the other object. This may involve multi-precision comparisons such as in big_int == __int128.

template<class L, common-big-int-type-with<L> R> constexpr strong_ordering operator<=>(const L& x, const R& y) noexcept;

2 Returns: strong_ordering::less if the integer value of x is less than the integer value of y, strong_ordering::greater if the integer value of y is greater than the integer value of y, and strong_ordering::equal otherwise.

X.3.10 Binary operations [big.int.binary]

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);

1 Preconditions: For operator/ and operator%, the integer value of y is nonzero.

2 Returns: For each operator function template operator@ where @ is a placeholder for the respective token, a basic_big_int object whose integer value is that of performing the operation T(x) @ T(y), where T is a hypothetical signed integer type with infinite range.
[Note: Bitwise operations are performed as if on a two's-complement representation, where positive numbers have an infinite number of leading zeroes, and negative numbers have an infinite number of leading ones. — end note]

std::atomic has a similar specification with operator@; maybe copy the wording from there.

template<class T, signed-or-unsigned S> remove_cvref_t<T> operator<<(T&& x, S s);

3 Constraints: remove_cvref_t<T> is a specialization of basic_big_int.

4 Preconditions: s is greater or equal to zero.

5 Returns: A basic_big_int whose integer value is v×2s, where v is the integer value of x, and whose allocator is that of x.

template<class T, signed-or-unsigned S> remove_cvref_t<T> operator>>(T&& x, S s);

6 Constraints: remove_cvref_t<T> is a specialization of basic_big_int.

7 Preconditions: s is greater or equal to zero.

8 Returns: A basic_big_int whose integer value is v×2s rounded towards negative infinity, where v is the integer value of x, and whose allocator is that of x.

X.3.11 Hash support [big.int.hash]

template<size_t b, class A> struct hash<basic_big_int<b, A>>;

1 The specialization is enabled ([unord.hash]).

2 Remarks: Let o1 be an object of type basic_big_int<b1, A1>, and let o2 be an object of type basic_big_int<b2, A2>. If o1 and o2 have equal integer value ([big.int.class]), then hash<basic_big_int<b1, A1>>()(o1) equals hash<basic_big_int<b2, A2>>()(o2).

[Note: For an object x of integral type T, hash<T>()(x) can be unequal to hash<big_int>()(big_int(x)). — end note]

X.3.12 Literals [big.int.literal]

template<char... digits> constexpr big_int operator""n() noexcept(see below);

1 Let s be a character sequence obtained by concatenating the elements of digits.

2 Mandates: s matches the syntax of an integer-literal with no integer-suffix and containing no digit separators. ([lex.icon])

3 Returns: A big_int object whose integer value is that of s interpreted as an integer-literal.

4 Remarks: The function specialization has a non-throwing exception specification if the effective width of the integer value returned by a function call expression is less than or equal to big_int::inplace_bits.

This implies we have to perform two-stage parsing. We first parse the literal and see if it can be represented as big_int with SBO. If so, the specialization is noexcept. Otherwise, each invocation of the UDL needs to allocate memory.

Perhaps a good way of implementing this would be to have a variable template template<char... digits> big_int_parsed; which holds std::optional<std::big_int>, where a value is present only if the pre-parsed value fits in std::big_int without allocation.

[charconv]

Change the synopsis [charconv.syn] as follows:

#include <big_int> // see [big.int] namespace std { […] constexpr to_chars_result to_chars(char* first, char* last, // freestanding integer-type value, int base = 10); template<size_t inplace_bits, class Allocator> constexpr to_chars_result to_chars(char* first, char* last, const basic_big_int<inplace_bits, Allocator>& value, int base = 10); to_chars_result to_chars(char* first, char* last, // freestanding bool value, int base = 10) = delete; […] constexpr from_chars_result from_chars(const char* first, const char* last, // freestanding integer-type& value, int base = 10); template<size_t inplace_bits, class Allocator> constexpr from_chars_result from_chars(const char* first, const char* last, basic_big_int<inplace_bits, Allocator>& value, int base = 10); from_chars_result from_chars(const char* first, const char* last, // freestanding-deleted floating-point-type& value, chars_format fmt = chars_format::general); }

[numeric.int.div]

This is just a rough sketch of how to go about computing the quotient and remainder at the same time, which is extremely important to avoid overhead from performing division twice.

There is a proposal [P3724R3] in the pipeline which adds the initial set of functions.

template<class T> constexpr div_result<T> div_rem_to_zero(T x, T y); template<class L, class R> constexpr div_result<common-big-int-type<L, R>> div_rem_to_zero(L&& x, R&& y);

[numeric.abs]

std::abs currently sits in <cmath> or in [c.math.abs], and it seems a bit absurd to require <cmath> to pull in <big_int>. I think it would make more sense for <numeric> to also expose std::abs instead (it probably already does in many standard libraries), and then extend the overload set:

constexpr int abs(int j); constexpr long int abs(long int j); constexpr long long int abs(long long int j);

1 Effects: […]

2 Remarks: […]

template<class T> constexpr remove_cvref_t<T> abs(T&& x);

3 Constraints: remove_cvref_t<T> is a specialization of basic_big_int ([bit.int]).

4 Returns: -std::forward<T>(x) if the integer value of x is negative, and std::forward<T>(x) otherwise.

[numeric.sat.cast]

template<class R, class T> constexpr R saturating_cast(T x) noexcept;

1 Constraints: R and T are signed or unsigned integer types ([basic.fundamental]).

2 Returns: If x is representable as a value of type R, x; otherwise, either the largest or smallest representable value of type R, whichever is closer to the value of x.

template<class R, size_t b, class A> constexpr R saturating_cast(const basic_big_int<b, A>& x) noexcept;

3 Constraints: R is a signed or unsigned integer type ([basic.fundamental]).

4 Returns: If the integer value ([big.int.class]) v of x is representable as a value of type R, v; otherwise, either the largest or smallest representable value of type R, whichever is closer to v.

This is partially motivated by the fact that Boost's static_cast for cpp_int behaves in a saturating instead of truncating way. While that behavior is useful, it would be surprising to C++ users who expect conversions to truncate, and it would be inconvenient in generic code.

Therefore, saturating_cast can act as an unsurprising spelling.

[numeric.conversions]

constexpr string to_string(int val); constexpr string to_string(unsigned val); constexpr string to_string(long val); constexpr string to_string(unsigned long val); constexpr string to_string(long long val); constexpr string to_string(unsigned long long val); string to_string(float val); string to_string(double val); string to_string(long double val); template<size_t b, class A> constexpr string to_string(const basic_big_int<b, A>& val);

Returns: format("{}", val).

[…]

constexpr wstring to_wstring(int val); constexpr wstring to_wstring(unsigned val); constexpr wstring to_wstring(long val); constexpr wstring to_wstring(unsigned long val); constexpr wstring to_wstring(long long val); constexpr wstring to_wstring(unsigned long long val); wstring to_wstring(float val); wstring to_wstring(double val); wstring to_wstring(long double val); template<size_t b, class A> constexpr wstring to_wstring(const basic_big_int<b, A>& val);

Returns: format(L"{}", val).

This requires formatting support.

3. References

[N5032] Thomas Köppe. Working Draft, Programming Languages — C++ 2025-12-15 https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/n5032.pdf