Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 786 – Precision and modulo-precision flag format specifiers for integer fields

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

Table of Contents

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. 4 is thus too much of a ‘magic number’, and trying to counter that by being overly explicit with f"{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 sign format 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 into k ‘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 as f"{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 z flag, a negative integer x shall 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 z flag,