Implement `precision` format spec for `int` type data

Okay sorry for flip-flopping here: my previous message which suggested implementing ‘exact precision’ with . was based off if we had to choose only one behaviour for . for integers, lest we add some new syntax like ! to achieve both. I hadn’t considered that we could use the z format specifier (which is currently unused for integers) in order to implement both. So disregard my last message.

I think we do. . for precision and z. for modulo followed by precision.

I agree. f"{25:#.2x}" is '0x19' and f"{1025:#.2x}" should be '0x401', not an OverflowError / ValueError. So ‘precision’ . (without a z) will be identical to %-formatting. Although I said I was okay with f-strings / str.format differing from %-formatting, that was only if we had to choose exactly one behaviour, i.e. without z having an effect to activate twos-complements mode; there’s no point purposely making % and str.format differ from each other.

Yes.

  • f"{25:#.2x}" is "%#.2x" % 25 is '0x19'
  • f"{1025:#.2x}" is "%#.2x" % 1025 is '0x401'. No truncation to exactly 2 digits, as 2 is only the minimum, So the PR needs to be changed to not be an OverflowError / ValueError
  • f"{-1025:#.2x}" is "%#.2x" % -1025 is '-0x401'. Same behaviour as %-formatting. I don’t think I’ve ever used negative hex format, but there’s no point breaking compatibility if we can avoid it.
  • f"{1025:#.2}" is "%#.2d" % 1025 is '1025'. Normal.
  • f"{-1:#.4}" is "%#.4d" % -1 is '-0001'. Normal.

I disagree with this. This is like the ‘Minimal width binary representation’ I talked about in a previous message. It’s okay when you know what you’re looking for, but even I got confused at the example on GitHub

>>> format(-129, '.8b')
'101111111'

That threw me off, I started reading it as 256 + 127 = 383. That’s why I suggested against ‘minimal width binary representation’

So I would go back to my proposal to implement . as ‘precision’, behaviour 3, the same as %-formatting, and behaviour 4 (take modulo base ** n first, then pass to .) as z. instead of adding a new !.

Also

I think I made a strong case in OP that having to re-calculate the width to account for the prefix is un-user-friendly, so this whole proposal is motivated :sweat_smile:

One of my original examples was a hexdump function, which fails for any non-ASCII bytes if a signed range were enforced

def hexdump(b: bytes) -> str:
    return " ".join(f"{i:#.2x}" for i in b)
hexdump("привет".encode()) # ValueError: Expected integer in range(-2**7, 2**7)

The solution is to implement the z format specifier for ints iff used with precision as z. with behaviour 4 that I laid out here

TLDR

So tldr: I suggest implementing the previous proposal, but with z. instead of adding a new !

  • . should do the same job in f-strings and str.format as it does with %, precision being the number of digits, as laid out in OP
  • z. should take modulo first, moving x into range(0, base ** n), and then formats that with precision, ie f"{x:z.{n}{base}}" is f"{divmod(x, base ** n)[1]:.{n}{base}}"