How to Format Hexadecimal Numbers with Signs and Zero-Padding in fmtlib
Use the format string "{:+#010x}" where + forces a sign, # adds the 0x prefix, 0 enables zero-padding, and 10 sets the total field width.
Formatting integers as hexadecimal with explicit positive signs and zero-padding is essential for low-level debugging, memory dumps, and network protocol analysis. The fmtlib/fmt library provides precise control over these output characteristics through its type-safe format specification API. Understanding the internal mechanics—from the format_specs parser to the write_int implementation—ensures you can predict exactly how signs, prefixes, and padding interact.
Anatomy of the Format Specification
When you write fmt::format("{:+#010x}", 12345), the library parses the string between the braces into a format_specs object. This structure captures four critical properties:
- Sign: Controlled by
+(always show sign) or space (leading space for positive), stored informat_specs::signas defined ininclude/fmt/core.h. - Fill/Zero-Padding: The
0flag setsformat_specs::fillto'0', triggering zero-fill logic later in the pipeline. - Alternate Form: The
#flag indicates that hexadecimal numbers should include the0xor0Xprefix. - Width and Type: The integer
10sets the total field width, whilexspecifies lowercase hexadecimal presentation.
Implementation in the fmtlib Source Code
The conversion from format string to output characters traverses several internal functions in include/fmt/format.h and include/fmt/printf.h.
Detecting the Sign and Zero Flags
Legacy format parsing in include/fmt/printf.h (line 318) maps character flags to internal specifiers:
case '+': specs.set_sign(sign::plus); break;
For zero-padding, the library detects when the fill character is '0' at lines 2581–2583 of include/fmt/format.h:
const bool is_zero_fill = specs.fill_size() == 1 && specs.fill_unit<Char>() == '0';
When is_zero_fill is true, the padding logic knows to insert zeros between the sign/prefix and the digits rather than using the default space-based alignment.
Constructing the Sign and Prefix
The function make_write_int_arg (lines 42–46 in include/fmt/format.h) creates a packed representation of the prefix that includes both the sign and the hexadecimal base indicator. For positive values with the + flag, it constructs:
constexpr unsigned prefixes[4] = {0, 0, 0x1000000u | '+', 0x1000000u | ' '};
prefix = prefixes[static_cast<int>(s)];
If the alternate form (#) is requested for hexadecimal output, prefix_append (lines 3113–3115) merges the 0x or 0X characters into the existing prefix:
prefix_append(prefix, unsigned(specs.upper() ? 'X' : 'x') << 8 | '0');
This ensures the sign character appears first, followed immediately by the base prefix.
Writing the Padded Output
Finally, write_int (lines 3150–3152 in include/fmt/format.h) emits the formatted number. After writing the prefix (sign + 0x), it calculates the remaining padding required to reach the specified width and fills with zeros:
it = detail::fill_n(it, padding, static_cast<Char>('0'));
This step produces the final string where zeros appear between the prefix and the hexadecimal digits, ensuring the total output length matches the requested width.
Practical Examples
The following program demonstrates combining signs, zero-padding, and hexadecimal formatting:
#include <fmt/core.h>
#include <cstdint>
#include <iostream>
int main() {
std::int32_t pos = 12345; // 0x3039
std::int32_t neg = -12345; // -0x3039
// Sign, zero-padding, width 8, lower-case hex, no prefix
std::cout << fmt::format("{:+08x}\n", pos); // +0003039
std::cout << fmt::format("{:+08x}\n", neg); // -0003039
// Sign, zero-padding, width 10, alternate form (adds 0x)
std::cout << fmt::format("{:+#010x}\n", pos); // +0x0003039
std::cout << fmt::format("{:+#010x}\n", neg); // -0x0003039
// Space flag instead of plus, upper-case hex, width 12
std::cout << fmt::format("{: #012X}\n", pos); // 0X0003039
std::cout << fmt::format("{: #012X}\n", neg); // -0X0003039
}
Key observations from the examples:
"{:+08x}"produces+0003039because the width8includes the sign character but excludes the hexadecimal prefix."{:+#010x}"produces+0x0003039(10 characters total) because the width includes the sign, the0xprefix, and the digits."{: #012X}"uses a space for positive signs and upper-case letters, yielding0X0003039(note the leading space before0X).
Summary
- fmtlib stores format parameters in a
format_specsobject that captures sign, fill, width, and presentation type. - The
+flag forces explicit signs for positive numbers, parsed ininclude/fmt/printf.hand stored viaenum class signininclude/fmt/core.h. - Zero-padding occurs when
is_zero_filldetects a'0'fill character ininclude/fmt/format.h, ensuring zeros appear between the prefix and digits. - The
write_intfunction ininclude/fmt/format.hfinalizes output by combining the sign, optional0xprefix (added byprefix_append), and zero-filled digits to meet width requirements.
Frequently Asked Questions
Does the field width count the sign and the 0x prefix?
Yes. In fmtlib, the width parameter specifies the total number of characters in the final output, including the sign character and the 0x or 0X prefix added by the # flag. For example, "{:+#010x}" generates exactly 10 characters.
Can I use a space instead of a plus sign for positive numbers?
Yes. Replace the + with a space in the format string (e.g., "{: #08x}"). The library stores this as sign::space and inserts a leading space for positive values while negative values retain the minus sign.
What happens if I remove the zero flag but keep the width?
Without the 0 flag, fmtlib uses the default fill character (space) and aligns the output to the right. The padding spaces appear before the sign and prefix, resulting in output like " +0x3039" instead of "+0x0003039".
Which header should I include for hexadecimal formatting?
Include <fmt/core.h> for the main fmt::format function. The internal implementations in include/fmt/format.h and include/fmt/printf.h are pulled in automatically by the core header; you do not need to include them directly.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →