std :: big_int
- Document number:
- D4444R0
- Date:
2026-05-24 - 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
.
Other sections will be filled in over time.
Contents
Introduction
Infinite-precision integers in other languages
Motivation
std :: big_int is a vocabulary type
std :: big_int for convenience and correctness
std :: big_int is platform-dependent and hard to implement
std :: big_int should be a compiler intrinsic for constant evaluation
Design
std :: uint_multiprecision_t
Small object optimizations
Sign and magnitude
Naming
Implementation experience
Wording
[version.syn]
[big.int]
[charconv]
[numeric.int.div]
[numeric.abs]
[numeric.sat.cast]
[numeric.conversions]
References
1. Introduction
C++ currently provides no portable support for integers wider than 64 bits.
This is one of the oldest and most widely recognized gaps in the language,
which authors have attempted to fill several times:
[N1692] (2004), [N1744] (2005), and [N4038] (2014)
all proposed a class with infinite precision.
Each of these attempts failed —
not because there was a lack of interest or motivation,
but because the task turned out to be too great of a challenge.
is to
what is to :
sure, a fixed size is sufficient for some use cases,
but comes with a constant threat of overflow and undefined behavior,
and that threat is easily eliminated at some acceptable runtime cost.
1.1. Infinite-precision integers in other languages
The runtime cost of infinite-precision integers is often acceptable
to the point where the default integer
in many languages is not fixed-size.
Even when infinite-precision is not the default,
languages often provide an infinite-precision type in their standard library,
or there is a commonly used third-party library that the ecosystem uses:
| Language | Integer type / constraint | Kind |
|---|---|---|
| Python | builtin | |
| Ruby | builtin | |
| Lisp | builtin | |
| Scheme | builtin | |
| Racket | builtin | |
| Clojure | builtin | |
| Haskell | builtin | |
| Prolog | builtin | |
| Wolfram Language | builtin | |
| Maxima | builtin | |
| Erlang | builtin | |
| JavaScript / TypeScript | builtin, but not the default | |
| MATLAB | Symbolic Math Toolbox | standard library |
| Java / Kotlin | standard library | |
| Julia | standard library | |
| C# | standard library | |
| F# | standard library | |
| Visual Basic .NET | standard library | |
| Go | standard library | |
| Standard ML | standard library | |
| Perl | standard library | |
| PHP | GMP and BCMath | standard library |
| R | third-party package | |
| Swift | third-party package | |
| Rust | third-party package |
Beyond that,
there are also many programming languages where some third-party support is available
(e.g.
Furthermore,
even when the language doesn't provide an infinite-precision integer type,
this is typically part of the compiler regardless.
For example, MSVC, GCC, and LLVM all internally have an infinite-precision integer,
such as .
Any C23 compiler also needs such a type to implement optimizations
like constant folding for ,
unless it has a small .
Not only are infinite-precision integers available in many languages, they are also used frequently:
| GitHub code search | Amount of files |
|---|---|
|
|
1.1M |
|
|
762K |
|
|
389K |
|
|
171K |
2. Motivation
In terms of utility,
has two main use case:
-
The use case where integers are potentially big.
For example,
could be the result of parsing an integer in a config file, and while the numbers are likely small in practice, it is theoretically possible that the user puts a huge value into the config file.std :: big_int is useful in many scenarios because it eliminates the need for range checks, and it eliminates the possibility of undefined behavior through signed integer overflow. One could also describe this as thestd :: big_int correctness use case
. - The use case where integers are actually big. For example, when performing computations in cryptography, statistics, etc. where the numbers involved simply don't fit into 64-bit integers.
2.1. std :: big_int is a vocabulary type
An important reason why should be in the C++ standard library
is that it is a vocabulary type and may appear at library boundaries.
:
-
A library that loads config files (JSON, YAML, etc.)
can return deserialized integers as
.std :: big_int -
Those
objects are then processed by a numeric library that uses them in e.g. statistics.std :: big_int -
The results are then stored by another library that serializes
to e.g. CSV.std : big_int
In some sense,
is an even more important vocabulary type
than and
because there is no convenient fallback solution.
If those containers were not available,
users could still communicate at library boundaries by using pointers and sizes.
Strings can be passed to other libraries as a (and maybe ),
so while is an important container type,
it is arguably gratuitous as a vocabulary type.
By comparison,
there is no widely established convention for passing large integers between libraries in C++,
and could lay that foundation.
It is not a good way of passing huge integers between libraries in general because conversion to/from strings for huge integers is very expensive.
2.2. std :: big_int for convenience and correctness
Another reason why needs to be in the standard library
is that users would often avoid it otherwise,
even when it is the right tool for the job.
For example, it could be quite plausible that a library has to handle integers
with more than 64 bits in some edge cases,
but if the library author needs to add a dependency on the whole of Boost.Multiprecision
to replace 2-3 lines of code using with ,
they may just not do it.
C++ developers don't get to choose between a fixed-width type for performance and an infinite-precision type for correctness; they get to choose between a fixed-width type and adding a massive dependency for correctness, which they may not do, especially if that dependency is transitively forced onto their library users.
2.3. std :: big_int is platform-dependent and hard to implement
Another reason why needs to be in the standard library
is that it's extremely difficult to implement and optimize,
in part because the implementation depends heavily on the platform's hardware capabilities,
many of which are not exposed portably in the language.
-
x86_64 has a
mul instruction that yields both the low and the high bits of a multiplication; without utilizing that, multiprecision multiplication is much slower. -
x86_64 also has a
div instruction that takes a 128-bit dividend and a 64-bit divisor, which can be used to implement division of an arbitrary-length dividend much more efficiently. -
Funnel-shifting (basically a 128-bit shift)
is a common operation in multiprecision arithmetic,
and Clang recently got a
intrinsics that exposes the hardware instruction.__builtin_elementwise_funnel_shl -
Bitwise operations between
can be performed in parallel using SIMD intrinsics. While those can be portably implemented usingstd :: big_int now, that may add unnecessary overhead compared to using the underlying intrinsics directly.<simd> -
Conversions between
and floating-point types may be hardware-accelerated or backed by special routines in the runtime library. For example, to convertstd :: big_int tofloat , the conversion should go throughstd :: big_int (__fixunssfti →float ) followed by a conversion tounsigned __int128 . Note that an unsigned 128-bit integer is sufficient to represent any finite binary32 floating-point number without the fractional part.std :: big_int -
During constant evaluation,
if
is available, it is often faster to implement an operation in terms of_BitInt . For example, a 4096-bit division can be delegated entirely to compiler intrinsics by converting the dividend and divisor to_BitInt , performing the division in terms of e.g._BitInt ( 4096 ) operations. This completely avoids the need for constant evaluation of complex division routines.llvm :: APInt
In general, the intrinsics necessary to implement efficiently
vary wildly from compiler to compiler
and from architecture to architecture.
It also varies a lot whether those intrinsics can be used during constant evaluation,
and these implementation details are subject to frequent change and added features.
Our [Reference-Impl] of is a sea of
and checks.
Overall, it is practically impossible or at least extremely difficult for a third-party library to keep up with all these details to implement the type optimally, across all compilers and all supported architectures. Features like these should ideally live directly in the compiler or in the standard library.
2.4. std :: big_int should be a compiler intrinsic for constant evaluation
While a portable implementation of is possible,
an ideal implementation for constant evaluation simply delegates
to the compiler's internal infinite-precision integer type (e.g. in LLVM).
This is because constant evaluation is relatively expensive
compared to highly optimized multiprecision code in the compiler internals.
A third-party library such as Boost.Multiprecision
does not have the luxury of adding new compiler intrinsics,
so the implementation quality could never match the potential of .
A closely related issue is the implementation of the user-defined literal, which has the following interface in our proposal:
The problem is that there is no other form of
that provides the obvious syntax,
and the form requires manual parsing of the digits during constant evaluation,
rather than delegating this to the compiler.
It would be much better if parsing could be done in the compiler to provide a form such as:
With this form,
- digit separators would be skipped automatically,
- base prefixes like
would be handled by the compiler, and0x - forming the limb array would be done by the compiler, which is the expensive part,
- template instantiations would be avoided completely.
That being said,
this new form of is not part if this proposal,
but it does demonstrate why some compiler support is needed.
3. Design
The most important anchor points of the design are:
-
should be elastic; that is, it should grow to the necessary size required to fit the result of an arithmetic operationstd :: big_int -
should support custom allocatorsstd :: big_int -
should exposestd :: big_int small object optimization
to the user
This culminates in the following library declarations:
Conceptually, a holds:
The underlying arithmetic is then implemented to operate on limb arrays,
i.e. arrays of objects,
agnostic of whether the integer value is store in-place or dynamically.
3.1. std :: uint_multiprecision_t
A user-facing integer type alias introduced by this paper is ,
which is an unsigned integer type with the following important properties:
-
has no padding bits. Padding bits in a limb type would be wasteful and make things more difficult both for users and for implementations.std :: uint_multiprecision_t -
is the widest type that has arithmetic hardware support.std :: uint_multiprecision_t -
has no explicit minimum width, but effectively cannot be narrower thanstd :: uint_multiprecision_t due to being unpadded.unsigned char
In summary, it is the unsigned integer type most suitable for multiprecision.
Curiously, there is no existing type with the desired properties
despite the abundance of type aliases in and :
-
is at least 32 bits wide, which means that on 16-bit architectures, the multiprecision code would have two layers: the emulation of 32-bit integers on 16-bit hardware, and then the multiprecision ofstd :: int_fast32_t operations. This is highly questionable because the point ofstd :: big_int is to have a type whose operations correspond directly to hardware instructions. Also,std :: uint_multiprecision_t is not consistently 64 bits wide on targets with 64-bit arithmetic support.std :: int_fast32_t -
often could be used asstd :: size_t , but on targets like WASM32, it is only 32 bits wide despite WASM32 having 64-bit arithmetic instructions forstd :: uint_multiprecision_t i64 . It would be extremely wasteful to only use 32-bit operations when 64 bits are available.
3.2. Small object optimizations
(or generally, infinite-precision integer types)
benefit from small object optimization
perhaps more than any other vocabulary type.
This is because with a relatively small amount of in-place storage
(e.g. 8 bytes for 64-bit numbers),
a lot of applications would never need to allocate dynamic storage to hold the value,
or only for extreme and unusual program inputs.
Furthermore,
once the numbers are huge enough to require dynamic allocation,
the cost of multiprecision arithmetic often hugely outweighs
the small amount of overhead caused by having a few unused bytes of in-place storage.
,
but many strings exceed the in-place capacity (around 20 bytes),
so dynamic allocation for fairly short strings remains common.
The optimization is also necessary to prevent excessive allocations for small intermediate results.
For example, even for huge inputs and ,
the difference , the quotient ,
or the remainder could still be fairly small numbers (possibly zero),
and it is wasteful to dynamically allocate for tiny integers.
Because performing this optimization for is such a no-brainer
and any reasonable implementation should have it,
we propose to expose it to the user via the template parameter.
This parameter specifies the minimum integer width that
must be able to represent without dynamic allocation.
While should be used in most cases and provides a reasonable default,
there may be scenarios where
-
on 32-bit platforms, the capacity could be lowered to
or raised to32 to prefer performance or correctness,64 -
on platforms where allocations are particularly expensive
or where dynamic storage is very limited,
the capacity could be raised to
or even128 .256
is always 64 regardless of architecture.
On 64-bit, our layout is as follows:
This layout ensures that
- is two pointers large,
- is aligned to one pointer,
- is nothrow-movable and trivially relocatable,
- can represent both
andint64_t values without allocating, anduint64_t - can represent values with up to bits (137 billion bits).
3.3. Sign and magnitude
uses a sign-and-magnitude representation
(as opposed to e.g. two's complement),
and this is exposed to the user.
While it may seem like constraining implementation freedom unnecessarily,
the chosen representation impacts the complexity requirements of various operations.
For example, if the sign bit is separately stored,
then negation can be implemented in constant time by just flipping the sign bit,
whereas for two's complement,
it requires linear time because bits of the magnitude need updating.
Most implementations of infinite-precision integers use sign-and-magnitude representation, as does our [Reference-Impl]. This is not only due to those beneficial complexity requirements, but also because it simplifies the implementation greatly. For example: multiplicative operators like multiplication and division can be implemented solely in terms of the magnitude, and the sign bit of the result can be computed separately.
The only major downside of sign-and-magnitude representation is that it requires
emulation of two's complement for bitwise operations,
but this is relatively cheap and does not require an additional pass over the data.
That is, for example, to compute with negative inputs,
the implementation negates the limbs of negative inputs on the fly
and also negates the final result if the result is negative.
is equivalent to .
In bitwise operations,
negative inputs are treated as if they were preceded by an infinite sequences of leading ones, and
positive inputs are treated as if they were preceded by an infinite sequence of leading zeros.
3.4. Naming
The name was chosen because it meets user expectations
and makes its design instantly clear.
In languages where infinite-precision integers are not built-in,
the name is virtually always some variant of big int
or big integer
.
The name is also immediately recognizable
as supporting infinite-precision arithmetic and as growing elastically as needed.
Previous proposals used the name ,
but this name doesn't convey the design well,
and is too similar to concept names like .
4. Implementation experience
Our reference implementation can be found at [Reference-Impl].
5. Wording
The changes are relative to [N5032].
[version.syn]
Add a feature-test macro to [version.syn] as follows:
[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
defines a class template for performing arbitrary-precision integer arithmetic, as well as related type aliases.<big_int> X.2 Header
synopsis [big.int.syn]<big_int> #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
denotes a standard unsigned or extended unsigned integer type ([basic.fundamental]) which has no padding bits.uint_multiprecision_t 2 Recommended practice:
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.uint_multiprecision_t
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
despiteuint8_t andsize_t being wider.int - WASM32 only has 32-bit addressable memory and has a 32-bit
; however, WASM32 supports 64-bit arithmetic instructions, so the appropriate limb type there issize_t .uint64_t - In most cases, the limb type can simply be
.size_t X.3 Class template
[big.int.class]basic_big_int 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
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 abasic_big_int is either represented using subobjects nested within abasic_big_int or represented within storage obtained from the givenbasic_big_int ; the sign of the integer value is represented separately.Allocator 2 The effective width of an integer value is the width of the smallest hypothetical unsigned integer type ([basic.fundamental]) able to represent the magnitude of .
3 Template parameter
specifies the minimum width of integers that amin_inplace_capacity shall be capable of representing using a subobject nested within.basic_big_int 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]).min_inplace_capacity
The design aspiration is that is capable of storing any standard or extended integer type without the use of allocations, includingbasic_big_int and__int128 . This is easily possible by just putting the proper integer type into aunsigned __int128 with the pointer to allocated data.union It is theoretically possible to use a greater limit and store a
within theuint_multiprecision_t [ ] , 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.basic_big_int 4 During constant evaluation, if the effective width of the integer value of a
is less than or equal to itsbasic_big_int member, the object shall not hold an allocation following any operation.inplace_bits
[Note: The behavior is as ifwas called following every operation that may allocate. — end note]shrink_to_fit ( )
This ensures that even without non-transient allocations, as long as the value can be stored directly in the object, you can create a variable. Otherwise, it would be possible to hold e.g. the valueconstexpr big_int in dynamic storage, even though it can be represented without allocations.123 Giving the implementation the freedom to hold an
unnecessary allocationstill makes sense because that allocation may be reused at a later point.5 The representation of a
object is the sequence ofbasic_big_int elements that collectively represents the integer value of that object. Unless otherwise stated,uint_multiprecision_t
- any member function or member function template without
qualifier, andconst - any free function or specialization of a function template that has a parameter of type
rvalue reference tobasic_big_int described in [big.int] invalidates the representation of the object, meaning that results previously returned by
are no longer valid.representation ( ) 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
is the amount ofinplace_representation_capacity nested within auint_multiprecision_t object and which participate in representing its integer value.basic_big_int 2 Remarks: The value of
shall be at leastinplace_representation_capacity .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 ifis a signed or unsigned integer type ([basic.fundamental]).T template < class T > concept arbitrary - integer = see below ; 2 The exposition-only concept
arbitrary-integer is satisfied and modeled if and only ifis either a signed or unsigned integer type ([basic.fundamental]) or a specialization ofremove_cvref_t < T > .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 ifis either a cv-unqualified arithmetic type ([basic.fundamental]) or a specialization ofremove_cvref_t < T > .basic_big_int template < class L , class R > using common - big - int - type = see below ; 4 Let
beLT , and letremove_cvref_t < L > beRT .remove_cvref_t < R > 5 Result:
- If
andLT are the same specialization ofRT ,basic_big_int ;LT - otherwise, if
is a specialization ofLT andbasic_big_int is a signed or unsigned integer type ([basic.fundamental]),RT ;LT - otherwise, if
is a specialization ofRT andbasic_big_int is a signed or unsigned integer type ([basic.fundamental]),LT ;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 isiftrue is a signed or unsigned integer type whose width is less than or equal toremove_cvref_t < T > , andinplace_bits otherwise.false 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
. Initializes the allocator tox .x . get_allocator ( ) 4 Throws: Nothing if the effective width of the integer value of
is less than or equal tox ; otherwise, exceptions thrown during allocation.inplace_bits constexpr basic_big_int ( basic_big_int && x ) noexcept ; 5 Effects: Initializes the integer value to that of
. Initializes the allocator tox .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:
isis_same_v < basic_big_int , remove_cvref_t < T >> .false
This constraint ensures that there is no ambiguity with the copy constructor or move constructor. Also note that we support construction from with other allocators.basic_big_int 7 Preconditions: If
is a floating-point type, the value ofremove_cvref_t < T > is finite.x 8 Effects: If
is an integral type or a specialization ofremove_cvref_t < T > , initializes the integer value to that ofbasic_big_int . Otherwise,x is a floating-point type, and this object is initialized to the integer value obtained by discarding the fractional part ofremove_cvref_t < T > .x 9 Throws: Nothing if the effective width of the integer value this object is initialized with is less than or equal to
; otherwise, exceptions thrown during allocation.inplace_bits 10 Remarks: The constructor is explicit if
is neither a signed or unsigned integer type ([basic.fundamental]) norremove_cvref_t < T > .basic_big_int < inplace_bits , Allocator >
The design goal here is to permit conversion from any arithmetic type as well as for specializations with other allocators, but to make allocator mixing and floating-point conversions explicit. Also explicit is the conversion from character types tobasic_big_int , which is arguably needed because character types and integers are used in different domains.basic_big_int template < arbitrary-arithmetic-type T > constexpr basic_big_int ( const T & x , const Allocator & a ) noexcept ( no-alloc-constructible-from < T > ) ; 11 Preconditions: If
is a floating-point type, the value ofremove_cvref_t < T > is finite.x 12 Effects: If
is an integral type or a specialization ofremove_cvref_t < T > , initializes the integer value to that ofbasic_big_int . Otherwise,x is a floating-point type, and this object is initialized to the integer value obtained by discarding the fractional part ofremove_cvref_t < T > . Initializes the allocator tox .a 13 Throws: Nothing if the effective width of the integer value this object is initialized with is less than or equal to
; otherwise, exceptions thrown during allocation.inplace_bits 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
, where the first element inr holds the least significant part of the concatenated base-2 representation. Ifr 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 toranges :: range_value_t < R > .a 15 Throws: Nothing if the effective width of the combined integer value is less than or equal to
; otherwise, exceptions thrown during allocation.inplace_bits X.3.4 Operations [big.int.ops]
constexpr span < const uint_multiprecision_t > representation ( ) const noexcept ; 1 Returns: A
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. Thespan of the result issize ( ) .div_to_pos_inf ( representation_size ( ) , numeric_limits < uint_multiprecision_t > :: digits )
is added by [P3724R3]. I would expect it to be available by the timediv_to_pos_inf is standardized.big_int 2 Remarks: If the integer value is greater or equal to zero,
shall have the same integer value.basic_big_int ( from_range , representation ( ) , get_allocator ( ) )
[Note: Consequently, elements of typethat 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]uint_multiprecision_t
This getter single-handedly imposes a huge amount of constraints on the implementation:
needs to store abasic_big_int of dynamically allocated data and ofunion to make the value accessible viauint_multiprecision_t .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,
; otherwise , where is the integer value.0
The result is identical to for a hypothetical signed integer typestd :: bit_width ( U ( std :: abs ( T ( ) ) ) ) with infinite range and a hypothetical unsigned integer typeT 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 ofU 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,
; otherwise1 .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
objects that can be part of the representation.uint_multiprecision_t 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:
, wheremax ( inplace_representation_capacity , dynamic-representation-capacity ( ) * numeric_ limits<uint_multiprecision_t>::digits) is the number of currently allocateddynamic-representation-capacity ( ) objects.uint_ multiprecision_tconstexpr 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
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 ofbasic_big_int .reserve 12 Postconditions:
is greater or equal to the argument ofcapacity ( ) if reallocation happens; and equal to the previous value ofreserve otherwise.capacity ( ) 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 frees the allocation and stores the integer value within theshrink_to_fit object. Otherwise,basic_big_int is a non-binding request to reduceshrink_to_fit tocapacity ( ) . It does not increasesize ( ) , but may reducecapacity ( ) causing reallocations.capacity ( ) 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:
isis_same_v < basic_big_int , remove_cvref_t < T >> .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
be a hypothetical signed integer type with sufficient width to represent theU 's integer value .basic_big_int 2 Constraints:
is a cv-unqualified arithmetic type ([basic.fundamental]).T 3 Returns:
.static_cast < T > ( static_cast < U > ( ) )
[Note: IfisT , the result isbool if is nonzero andtrue otherwise ([conv.integral]). Iffalse is a floating-point type, the result value is determined as if by floating-integral conversion ([conv.fpint]). — end note]T 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
prior to the call.representation ( ) 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.alias]big_int using big_int = basic_big_int < see below > ; 1 Result: A specialization of
with an implementation-defined argument for thebasic_big_int constant template parameter, chosen so that equalsmin_inplace_capacity .basic_big_int < > :: inplace_ bits2 Recommended practice: should be sufficiently large so that
may represent the value of all commonly used integer types without allocating.big_int 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:
if the integer value oftrue is equal to the integer value ofx , andy otherwise.false
The requirement means that it's not a valid implementation strategy to wrap any integer innoexcept 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 inbasic_big_int .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:
if the integer value ofstrong_ordering :: less is less than the integer value ofx ,y if the integer value ofstrong_ordering :: greater is greater than the integer value ofy , andy otherwise.strong_ordering :: equal 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
andoperator / , the integer value ofoperator % is nonzero.y 2 Returns: For each operator function template
whereoperator @@is a placeholder for the respective token, aobject whose integer value is that of performing the operationbasic_big_int , whereT ( x ) @T ( y ) is a hypothetical signed integer type with infinite range.T
[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]
has a similar specification withstd :: atomic ; maybe copy the wording from there.operator @template < class T , signed-or-unsigned S > remove_cvref_t < T > operator << ( T && x , S s ) ; 3 Constraints:
is a specialization ofremove_cvref_t < T > .basic_big_int 4 Preconditions:
is greater or equal to zero.s 5 Returns: A
whose integer value is , where is the integer value ofbasic_big_int , and whose allocator is that ofx .x template < class T , signed-or-unsigned S > remove_cvref_t < T > operator >> ( T && x , S s ) ; 6 Constraints:
is a specialization ofremove_cvref_t < T > .basic_big_int 7 Preconditions:
is greater or equal to zero.s 8 Returns: A
whose integer value is rounded towards negative infinity, where is the integer value ofbasic_big_int , and whose allocator is that ofx .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 be an object of type
, and let be an object of typebasic_big_int < , > . If and have equal integer value ([big.int.class]), thenbasic_big_int < , > equalshash < basic_big_int < , >> ( ) ( ) .hash < basic_big_int < , >> ( ) ( ) [Note: For an object of integral type
,T can be unequal tohash < T > ( ) ( ) . — end note]hash < big_int > ( ) ( big_int ( ) ) X.3.12 Literals [big.int.literal]
template < char ... digits > constexpr big_int operator " " n ( ) noexcept ( see below ) ; 1 Let be a character sequence obtained by concatenating the elements of
.digits 2 Mandates: matches the syntax of an
integer-literal with nointeger-suffix and containing no digit separators. ([lex.icon])3 Returns: A
object whose integer value is that of interpreted as anbig_int 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 with SBO. If so, the specialization isbig_int . Otherwise, each invocation of the UDL needs to allocate memory.noexcept Perhaps a good way of implementing this would be to have a variable template
which holdstemplate < char ... digits > big_int_parsed ; , where a value is present only if the pre-parsed value fits instd :: optional < std :: big_int > without allocation.std :: big_int
[charconv]
Change the synopsis [charconv.syn] as follows:
[numeric.int.div]
There is a proposal [P3724R3] in the pipeline which adds the initial set of functions.
[numeric.abs]
currently sits in or in [c.math.abs],
and it seems a bit absurd to require to pull in .
I think it would make more sense for to also expose 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: […]
3
Constraints:
is a specialization of ([bit.int]).
4
Returns:
if the integer value of is negative, and
otherwise.
[numeric.sat.cast]
template < class R , class T > constexpr R saturating_cast ( T x ) noexcept ; 1 Constraints:
andR are signed or unsigned integer types ([basic.fundamental]).T 2 Returns: If
is representable as a value of typex ,R ; otherwise, either the largest or smallest representable value of typex , whichever is closer to the value ofR .x
3
Constraints:
is a signed or unsigned integer type ([basic.fundamental]).
4
Returns:
If the integer value ([big.int.class]) of
is representable as a value of type , ;
otherwise, either the largest or smallest representable value of type ,
whichever is closer to .
for 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, can act as an unsurprising spelling.
[numeric.conversions]
Returns:
.
[…]
Returns:
.