PEP 786 – Precision and modulo-precision flag format specifiers for integer fields
- Author:
- Jay Berry <email at jb2170.com>
- Sponsor:
- Alyssa Coghlan <ncoghlan at gmail.com>
- Discussions-To:
- Discourse thread
- Status:
- Draft
- Type:
- Standards Track
- Created:
- 04-Apr-2025
- Python-Version:
- 3.15
- Post-History:
- 14-Feb-2025, 09-Apr-2026
Abstract
This PEP proposes implementing the standard format specifiers . and z
of PEP 3101 for integer fields as “precision” and “modulo-precision”
respectively. Both are presented together in this PEP as the alternative
rejected implementations entail intertwined combinations of both.
. (“precision”) shall format an integer to a specified minimum number of
digits, identical to the behavior of old-style % formatting. This shall be
implemented for all integer presentation types except 'c'.
z (“modulo-precision”) shall be permitted as an optional “modulo” flag
when formatting an integer with precision and one of the binary, octal, or
hexadecimal presentation types (bases that are powers of two). This first
reduces the integer into range(base ** precision) using the % operator.
The result is a predictable two’s complement style formatting with the exact
number of digits equal to the precision.
This PEP amends the clause of PEP 3101 which states “The precision is ignored for integer conversions”.
Rationale
When string formatting integers in binary octal and hexadecimal, one often
desires the resulting string to contain a guaranteed minimum number of digits.
For unsigned integers of known machine-width bounds (for example, 8-bit bytes)
this often also ends up the exact resulting number of digits. This has
previously been implemented in the old-style % formatting using the
. “precision” format specifier, closely related to that of the C
programming language.
>>> "0x%.2x" % 15
'0x0f' # two hex digits, ideal for displaying an unsigned byte
>>> "0o%.3o" % 18
'0o022' # three octal digits, ideal for displaying a umask or file permissions
When PEP 3101 new-style formatting was first introduced, used in
str.format and f-strings, the format specification was
simple enough that the behavior of “precision” could be trivially emulated with
the width format specifier. Precision therefore was left unimplemented and
forbidden for int fields. However, as time has progressed and new format
specifiers have been added, whose interactions with width noticeably
diverge its behavior away from emulating precision, the readmission of
precision as its own format specifier, ., is sufficiently warranted.
The width format specifier guarantees a minimum length of the entire
replacement field, not just the number of digits in a formatted integer.
For example, the wonderful # specifier that prepends the prefix of the
corresponding presentation type consumes from width:
>>> x = 12
>>> f"0x{x:02x}" # manually specifying '0x' prefix
'0x0c' # two hex digits :)
>>> f"{x:#02x}" # use '#' format specifier to output '0x' automatically
'0xc' # only one hex digit :(
>>> f"{x:#08b}"
'0b001100' # we wanted 8 bits, not 6 :(
One could attempt to argue that since the length of a prefix is known to always be 2, it can be accounted for manually by adding 2 to the desired number of digits. Consider however the following demonstrations of why this is a bad idea:
- By correcting the second example to
f"{x:#04x}", at a glance this looks like it may produce four hex digits, but it only produces two. This is bad for readability.4is thus too much of a ‘magic number’, and trying to counter that by being overly explicit withf"{x:#0{2+2}x}"looks ridiculous. - In the future it is possible that a type specifier may be added with a prefix not of length 2, meaning the programmer has to calculate the prefix length, rather than Python’s internal string formatting code handling that automatically.
- Things get more complicated when using the
signformat specifier,f"{x: #0{1+2+2}x}"required to produce' 0x0c'. - Things get even more complicated when introducing a
grouping_option, for example formatting an integer intok‘word’ segments joined by_:x = 3735928559; k = 2; f"{x: #0{1+2+4*k+(k - 1)}_x}"is required to produce' 0xdead_beef'. Surely this would be easier to write with precision asf"{x: #_.8x}"?
It is clear at this point that the reduction of complexity that would be
provided by precision’s implementation for int fields would be beneficial
to any user. Nor is this proposal a new special-case behavior being demanded
exclusively at the behest of int fields: the precision token . is
already implemented as prescribed in PEP 3101 for str data to truncate
the field’s length, and for float data to ensure that there are a fixed
number of digits after the decimal point, eg f"{0.1+0.2: .4f}" producing
' 0.3000'. Thus no new tokens need adding to the format specification
because of this proposal, maintaining its modest size.
For the sake of completion, and lack of any reasonable objection, we propose
that precision shall work also in decimal, base 10. Explicitly, the integer
presentation types laid out in PEP 3101 that are permitted to implement
precision are 'b', 'd', 'o', 'x', 'X', 'n',
and '' (None). The only presentation type not permitted is
c (‘character’), whose purpose is to format an integer to a single Unicode
character, or an appropriate replacement for non-printable characters, for
which it does not make sense to implement precision. In the event that new
integer presentation types are added in the future, such as 'B' and 'O'
which mutatis-mutandis could provide the same behavior as 'X' (that is a
capitalized prefix and digits), their addition should appropriately consider
whether precision should be implemented or not. In the case of 'B' and 'O'
as described here it would be correct to implement precision. A ValueError
shall be raised when precision is attempted to be used for invalid integer
presentation types.
Precision For Negative Numbers
So far in this PEP we have cautiously avoided talking about the formatting of negative numbers with precision, which we shall now discuss.
Short Verdict
We desire two behaviors, which motivates the implementation of a flag z to
toggle on the latter’s behavior:
- For precision without the
zflag, a negative integerxshall be formatted with a negative sign and the digits of-x’s formatting. This is the same friendly behavior as old-style%formatting.For example
f"{-12:#.2x}"shall produce'-0x0c', equivalent to"%#.2x" % -12. - For precision with the
zflag,