<format> Support
beman::big_int specializes std::formatter for every basic_big_int
specialization, for both char and wchar_t, so a big integer formats
wherever a built-in integer does: std::format, std::format_to,
std::format_to_n, std::formatted_size, std::vformat, and anything else
built on std::formatter. The format specification is the standard integer
one, with the same fields, the same defaults, and the same errors, so a spec
that formats an int formats a basic_big_int the same way, only without a
fixed-width ceiling on the value.
Include the formatter with <beman/big_int/format.hpp>, or transitively
through the umbrella <beman/big_int.hpp>. The header is self-guarding: its
entire contents are conditioned on
#if __has_include(<format>) && defined(__cpp_lib_format) && __cpp_lib_format >= 201907L
so on a standard library that does not provide <format> the header is empty
and including it is harmless. basic_big_int has no stream inserter of its
own; to write a value to a std::ostream, format it or convert it with
to_string.
Synopsis
namespace std {
template <std::size_t b, class L, class A, class charT>
struct formatter<beman::big_int::basic_big_int<b, L, A>, charT> {
template <class ParseContext>
constexpr typename ParseContext::iterator parse(ParseContext& ctx);
template <class FormatContext>
typename FormatContext::iterator format(const beman::big_int::basic_big_int<b, L, A>& value,
FormatContext& ctx) const;
};
} // namespace std
The specialization is written in terms of basic_big_int, so it covers
big_int, pmr::big_int, and every other specialization, whatever the
in-place bit count, limb type, and allocator. charT may be char or
wchar_t.
Format specification
The grammar is the standard integer one:
format-spec ::= [[fill]align][sign]["#"]["0"][width]["L"][type]
fill ::= any single character other than '{' or '}'
align ::= '<' | '>' | '^'
sign ::= '+' | '-' | ' '
width ::= positive-integer | '{' [arg-id] '}'
type ::= 'b' | 'B' | 'd' | 'o' | 'x' | 'X' | 'c'
Every field is optional, and an empty specification ({} or {:}) formats the
value in decimal with no padding.
Fill and alignment
align is < for left, > for right, and ^ for centered, and is preceded
by an optional fill character that defaults to a space. A field wider than
the formatted value is padded with fill on the side or sides the alignment
selects; a value at least as wide as the field is written in full and is never
truncated.
Without an explicit alignment the default is right for every numeric
presentation type and left for the c type, matching the standard formatters.
The fill is a character, not a code unit: a UTF-8 format string may spell it in
up to four bytes, and a UTF-16 one may spell it as a surrogate pair. Each
padding position takes one whole fill character, and a byte sequence that does
not spell a character, such as a truncated or overlong UTF-8 sequence, is
rejected with std::format_error. As with the standard formatters, the fill is
one character and not one grapheme cluster, so a letter followed by a combining
mark is not a valid fill.
Sign
- (the default) writes a sign only for a negative value, ` writes a sign
for every value, and a space writes a space in place of the `. Zero is
non-negative, so {:+} renders it as +0. The sign option is ill-formed with
the c type.
Alternate form
# prepends the base prefix: 0b for b, 0B for B, 0x for x, 0X
for X, and a single 0 for o, except that a value of zero takes no octal
prefix. It has no effect on d or on the default type, and is ill-formed with
the c type.
Zero padding
0 selects sign-aware zero padding: the field is filled with 0 characters
placed after the sign and after any base prefix, so {:#06x} renders 255 as
0x00ff. An explicit alignment overrides it, in which case the fill character
and alignment are used as written. 0 is ill-formed with the c type.
Width
The minimum field width is either a positive decimal integer written in the
spec, or a nested replacement field. A leading 0 is always consumed as the
zero-padding flag, so a static width never begins with 0. A nested {} takes
the next argument in sequence, and a nested {n} takes argument n; the
width argument must be of a non-bool integral type and hold a non-negative
value.
A width, or an argument index, too large to represent in a std::size_t is
ill-formed.
Locale
L inserts the locale’s digit group separators into the digits, using
std::numpunct<charT>::grouping() and std::numpunct<charT>::thousands_sep()
of the locale passed to the formatting function, or of the global locale where
the call passes none. The classic locale has empty grouping, so L is a no-op
there.
Grouping applies to the digits alone, not to the sign or the base prefix, and,
as with the standard integer formatters, applies in every base rather than in
decimal only. Where L is combined with 0, the separators count toward the
field width and the padding zeros themselves are not grouped, so a width of 12
renders 1234 as 00000001,234 under a locale that groups in threes with a
comma. L is accepted with the c type, where it has no effect.
Presentation type
| Type | Meaning |
|---|---|
none, |
Decimal. |
|
Binary. With |
|
Binary. With |
|
Octal. With |
|
Hexadecimal with the lowercase digits |
|
Hexadecimal with the uppercase digits |
|
The value as a single character of type |
The c type requires the value to lie in
[std::numeric_limits<charT>::min(), std::numeric_limits<charT>::max()], so a
value that needs more than one limb is always out of range. Following the resolution of LWG 3644, c is not an integer presentation type,
so combining it with the sign option, #, or 0 is ill-formed even though
some standard libraries still accept those combinations on the built-in types.
parse and format
parse
template <class ParseContext>
constexpr typename ParseContext::iterator parse(ParseContext& ctx);
Parses the format specification from [ctx.begin(), ctx.end()) and stores it
in the formatter.
-
Preconditions: None.
-
Returns: An iterator past the end of the parsed specification, that is, one pointing at the terminating
}or atctx.end(). -
Throws:
std::format_errorif the specification is not valid; see Errors. -
Remarks: The function is usable in a constant expression, so an invalid specification in a call with a compile-time-checked format string is a compile-time error rather than a runtime one. Only a specification that is not known until runtime, such as one passed to
std::vformat, reaches the throwing path.
format
template <class FormatContext>
typename FormatContext::iterator format(const beman::big_int::basic_big_int<b, L, A>& value,
FormatContext& ctx) const;
Writes value, formatted according to the parsed specification, to
ctx.out().
-
Preconditions: None.
-
Returns: An iterator past the end of the output.
-
Throws:
std::format_errorif a dynamic width argument is invalid, or if thectype is used with a value that is out of range forcharT; otherwise, any exception thrown while allocating the intermediate digit string (for example,std::bad_alloc) or thrown by the output iterator. -
Remarks: The digits come from
to_string, so a multi-limb value is converted with the same sub-quadratic base conversion the rest of the library uses, and the conversion cost, rather than the padding, dominates a large format.
Errors
The formatter reports these conditions. Apart from the c combinations that
LWG 3644 made ill-formed, which some standard libraries still accept on the
built-in types, each is reported exactly where a standard integer formatter
reports it. The messages are listed without the `big_int format: ` prefix that
each of them carries:
| Condition | Reported by | Message |
|---|---|---|
An unknown presentation type |
|
|
A precision (a |
|
|
Characters left over before the closing |
|
|
A nested width field with no closing |
|
|
A width or argument index too large for |
|
|
The sign option combined with |
|
|
|
|
|
|
|
|
A width argument that is not of a non-bool integral type |
|
|
A negative width argument |
|
|
A |
|
|
Where the format string is checked at compile time, the parse conditions are
compile-time errors and never produce an exception. The remaining conditions
depend on the argument values, so they are reported when the value is
formatted.
Exception-free builds
Where the library is built with exceptions disabled, there is nothing to throw:
each of the conditions above calls std::abort() instead. The mode is deduced
in <beman/big_int/detail/config.hpp> from __EXCEPTIONS and, on MSVC,
_CPPUNWIND, and is recorded as BEMAN_BIG_INT_ALLOW_EXCEPTIONS or
BEMAN_BIG_INT_NO_EXCEPTIONS. This matches how the rest of the library
reports a length limit; see
the length limit.
Examples
using beman::big_int::big_int;
std::format("{}", big_int{-7}); // "-7"
std::format("{:5}", big_int{42}); // " 42", numbers align right by default
std::format("{:<5}", big_int{42}); // "42 "
std::format("{:^7}", big_int{42}); // " 42 "
std::format("{:*>6}", big_int{42}); // "****42"
std::format("{:+}", big_int{42}); // "+42"
std::format("{: }", big_int{42}); // " 42"
std::format("{:06d}", big_int{-42}); // "-00042", zeros follow the sign
std::format("{:#06x}", big_int{255}); // "0x00ff", zeros follow the prefix
std::format("{:<06d}", big_int{42}); // "42 ", an explicit align disables zero padding
std::format("{:#b}", big_int{5}); // "0b101"
std::format("{:#B}", big_int{5}); // "0B101"
std::format("{:#o}", big_int{64}); // "0100"
std::format("{:#o}", big_int{0}); // "0", zero takes no octal prefix
std::format("{:X}", big_int{255}); // "FF"
std::format("{:c}", big_int{65}); // "A"
std::format("{:5c}", big_int{65}); // "A ", the c type aligns left by default
std::format("{:{}}", big_int{42}, 6); // " 42", width from an argument
std::format("{:0{}x}", big_int{255}, 6); // "0000ff"
The value is arbitrary-precision throughout, so the same specifications hold for magnitudes no built-in type can express:
using namespace beman::big_int::literals;
const big_int value = 1_n << 128;
std::format("{}", value); // "340282366920938463463374607431768211456"
std::format("{:#x}", value); // "0x100000000000000000000000000000000"
With a locale whose numpunct groups digits in threes with a comma:
std::format(loc, "{:L}", big_int{1234567}); // "1,234,567"
std::format(loc, "{:012L}", big_int{1234}); // "00000001,234"
For a complete program, see the formatting and output example.
Wide characters
Everything above holds for wchar_t with a wide format string. The digits and
the base prefixes belong to the basic execution character set, so each
character has the same value in the wide result as in the narrow one; L reads
the std::numpunct<wchar_t> facet; and the c type accepts the range of
wchar_t rather than that of char.
std::format(L"{:#06X}", big_int{255}); // L"0X00FF"
std::format(L"{:*^8}", big_int{42}); // L"***42***"
std::format(L"{:c}", big_int{0x41}); // L"A"