Repository navigation
PEP 682: Format Specifier for Signed Zero #2295
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
f8b927c
a6bf8f3
e1ad4f9
3fbc1f6
7a86237
6522671
beb801d
8a58924
8fd0dd7
138ff46
0aadde6
866094b
e1ae1fa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,183 @@ | ||
| PEP: 682 | ||
| Title: Format Specifier for Signed Zero | ||
| Author: John Belmonte <john@neggie.net> | ||
| Sponsor: Mark Dickinson <dickinsm@gmail.com> | ||
| Discussions-To: | ||
| Status: Draft | ||
| Type: Standards Track | ||
| Content-Type: text/x-rst | ||
| Created: 29-Jan-2022 | ||
| Python-Version: 3.11 | ||
| Post-History: | ||
|
|
||
|
|
||
| Abstract | ||
| ======== | ||
|
|
||
| Though ``float`` and ``Decimal`` types can represent `signed zero`_, in many | ||
| fields of mathematics, negative zero is surprising or unwanted -- especially | ||
| in the context of displaying an (often rounded) numerical result. This PEP | ||
| proposes an extension to the `string format specification`_, allowing negative | ||
| zero to be normalized to positive zero. | ||
|
|
||
| .. _`signed zero`: https://en.wikipedia.org/wiki/Signed_zero | ||
| .. _`string format specification`: https://docs.python.org/3/library/string.html#formatstrings | ||
|
|
||
|
|
||
| Motivation | ||
| ========== | ||
|
|
||
| Here is negative zero: | ||
|
|
||
| .. code-block:: pycon | ||
|
|
||
| >>> x = -0. | ||
| >>> x | ||
| -0.0 | ||
|
|
||
| When formatting a number, negative zero can result from rounding. Assuming | ||
| the user's intention is truly to discard precision, the distinction between | ||
| negative and positive zero of the rounded result might be considered an | ||
| unwanted artifact: | ||
|
|
||
| .. code-block:: pycon | ||
|
|
||
| >>> for x in (.002, -.001, .060): | ||
| ... print(f'{x: .1f}') | ||
| 0.0 | ||
| -0.0 | ||
| 0.1 | ||
|
|
||
| There are various approaches to clearing the sign of a negative zero. It | ||
| can be achieved without a conditional by adding positive zero: | ||
|
|
||
| .. code-block:: pycon | ||
|
|
||
| >>> x = -0. | ||
| >>> x + 0. | ||
| 0.0 | ||
|
|
||
| To normalize negative zero when formatting, it is necessary to perform | ||
| a redundant (and error-prone) pre-rounding of the input: | ||
|
|
||
| .. code-block:: pycon | ||
|
|
||
| >>> for x in (.002, -.001, .060): | ||
| ... print(f'{round(x, 1) + 0.: .1f}') | ||
| 0.0 | ||
| 0.0 | ||
| 0.1 | ||
|
Comment on lines
+63
to
+69
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, this isn't ideal. It's a pythondotorg styling problem, I'd imagine - and hard to fix currently. A
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Grep shows only one other PEP using
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why would it be "pycon", and not "python"? It seems like all of the other PEPs don't specify a language, so I think you're right in removing it.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Support for specifying languages was only added very recently (by adding Pygments to the requirements file) -- as a PEP editor I would strongly encourage explicitness in language as it makes reading PEPs easier with correctly highlighted code. A
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A bit late now, sorry (I was invited to visit the SpaceX Starship production and launch site in Texas and thus was unavailable for the past week) but I can fully confirm everything @AA-Turner said, which was the basis of my initial implemented suggestion. In addition to the reasons he mentioned, being explicit about the language in the code block is also ensures the block will be highlighted correct regardless of what we decide to do in terms of the default syntax highlighter (none, auto, Python, etc). If
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you-- while we did revert the pycon block annotations for now, I agree that accurate block annotation is desirable and we'd like all the PEP documents to be doing that. Summary from the time of the decision (#2317 (comment)):
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but as I noted there, AFAIK all this is specific to the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I have avoided this topic as I don't want to spark a discussion. My current opinion is we should leave implicit literal block syntax as it is in most places, but update all cases where the language is not Python. This opinion is subject to change several times per arbitrary time period. This probably means we wouldn't update to
another debate for another time ;) A
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I share this opinion as well, and am willing to help implement if and when the time comes for this, though I think it could be limited to not include withdrawn, superceded, and otherwise inactive PEPs. For new PEPs I would favor the |
||
|
|
||
| What we would like instead is a first-class option to normalize negative | ||
|
mdickinson marked this conversation as resolved.
Resolved
|
||
| zero, on top of everything else that numerical string formatting already | ||
| offers. | ||
|
belm0 marked this conversation as resolved.
Resolved
|
||
|
|
||
|
|
||
| Rationale | ||
| ========= | ||
|
|
||
| There are use cases where negative zero is unwanted in formatted number | ||
| output -- arguably it's the more common case. Expanding the format | ||
|
belm0 marked this conversation as resolved.
Resolved
|
||
| specification is the best way to support this because number formatting | ||
| already incorporates rounding, and the normalization of negative zero must | ||
| happen after rounding. | ||
|
|
||
| While it is possible to pre-round and normalize a number before formatting, | ||
| it's tedious and prone to error if the rounding doesn't precisely match | ||
| that of the format spec. Furthermore, functions that wrap formatting would | ||
| find themselves having to parse format specs to extract the precision | ||
| information. For example, consider how this utility for formatting | ||
| one-dimensional numerical arrays would be complicated by such pre-rounding: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| def format_vector(v, format_spec='8.2f'): | ||
| """Format a vector (any iterable) using given per-term format string.""" | ||
| return f"[{','.join(f'{term:{format_spec}}' for term in v)}]" | ||
|
|
||
| The solution must be opt-in, because we can't change the behavior of | ||
| programs that may be expecting or relying on negative zero when formatting | ||
| numbers. | ||
|
|
||
| The proposed extension is intentionally ``[sign][z]`` rather than | ||
| ``[sign[z]]``. The default for ``sign`` (``-``) is not widely known or | ||
| explicitly written, so this avoids everyone having to learn it just to use | ||
| the ``z`` option. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a matter of draughting, so you're well within your rights as author to ignore. It does feel a slight jump in topic though -- the previous paragraphs discuss the format specification in the round, and why the solution must be invisible to those not using it, etc. This paragraph though skips detail of the current format specification or grammar of the mini-langauge itself, going straight into the nested vs sibiling approach. A possible solution would be to combine this paragraph with the preceeding, and say something along the lines of the solution must be opt in, and given that the negative option is the default we can't link the two, moving the detail of the grammar to the specification. Another solution is to move this entire paragraph to the specification -- this could be argued to be explanation of the choices in the specification rather than rationale for the chage itself.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you. This is related to the discussion with CAM on section ordering: #2295 (comment). I originally wrote the rationale section intending that it support the decisions of the specification, and it was placed after the specification.
Indeed, but as CAM cited from PEP 1:
This suggests that the rational supports the specification, and is not merely a rationale for the change itself. If it does support the specification, logically it should follow that section. I wish that the guidelines on section ordering gave me such leeway. Possible resolutions, with my preference first:
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the extracts, I missed that discussion. My own opinion is that clarity of expression is more important than adherence to the "rules". (That said, the "rules" exist for a reason!). I've also never been entirely sure of the difference between Motivation and Rationale. I'd say flip the order if it makes the most sense. @CAM-Gerlach -- would you have a strong objection to this? A
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My actual suggestion was to not include this paragraph, the one following, and optionally the one preceding it in the Rationale at all:
They are rather detailed and implementation-level (as @AA-Turner noticed), and refer to why specific potential alternatives were not adopted, which is exactly what belongs in the Rejected Ideas section (with subheadings for each separate idea)—which, indeed, goes after the Specification, where you want them. This just leaves the current mention of prior art (and any you add), the brief high-level rationale of the core design decision, and the counterpoint to why pre-rounding instead wouldn't work (the latter of which, if you really wanted, you could consider moving to Motivation or as a separate rejected idea), which makes much more sense as an introduction to the specification. If you prefer, you could also consider re-organizing those sub-parts a bit to put the pre-rounding approach first (why the status quo doesn't work), prior art section second (how did other languages tackle the problem) and the high-level description of your design last, which then smoothy outlines the rationale that led to the overall idea of the proposal, which then leads into the specification itself.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moving the Rationale after the Specification sounds good to me; I agree that clarity beats strict adherence to the rules.
Me neither, but there are two clearly distinct discussion items needed in a PEP like this: (1) the motivation for making a change (any change) in the first place, independent of the actual proposed solution, and (2) the reasons for choosing the particular solution that's being proposed from amongst other possible solutions. My best reading of PEP 12 is that these correspond to the Motivation and Rationale sections, respectively. And then if the Rationale section precedes the Specification section, it's a bit awkward to be referring to details of a specification that hasn't been presented yet.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
As I've been mentioning, the high level reasoning that led to the solution goes in the Rationale section, but the specific reasons why other possible solutions were not selected, and those dealing with the details of the specification, belong in the Rejected Ideas section, which goes after the Specification section. Is there a reason you'd rather not use that section for the more specific alternatives and details of the specification, while still keeping a short high-level rationale and comparison of prior art (which, certainly, should go before proposing what we're doing, its "prior" art after all) before? That seems like a good compromise that moves the stuff that you and Adam identified that doesn't really belong before the specification, after it, while still keeping the stuff that leads into it before and following PEP 1 and PEP 12 on the Rationale section itself?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
No - I'll step out of this and leave the wrangling to you, Adam and John. I don't really care much about the exact order of the sections or whether there's strict adherence to PEP 12 or not - I care much more that the resulting text is coherent and is meaningful when read from top to bottom.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also don't have a strong opinion, having said my piece above!
Seconded, although perhaps verboten for a pep editor to say :P A
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I would use the Rejected Ideas section if there was an idea that had legs and was actually rejected. To apply an opposite polarity to every design decision and render it as a rejected idea seems a little contrived. The items currently documented in Rationale, for the most part, had no reasonable alternatives-- and I touch on why that is for each. A (fictitious) example of something I'd put into Rejected Ideas would be "We explored automatically inferring suppression of negative zero based on whether the input was rounded, but it was found to be unreliable, and still may break existing programs." I appreciate that the PEP guidelines are an established thing, and I'm merely a PEP author. On the other hand, my eyes are fresh to this, and there may be ways to refine the guidelines. At the least, I've come upon some contradiction in the guidelines as to whether Rationale is that of the entire PEP or of the Specification. What I'd like to do from here is The attention from everyone towards making a good document has been impressive and welcome-- even if we have different views on it-- thank you.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I've addressed what Adam raised originally by moving relevant text into Specification. |
||
|
|
||
| While f-strings, built-in ``format()``, and ``str.format()`` can access | ||
| the new option, %-formatting cannot. There is already precedent for not | ||
| extending %-formatting with new options, as was the case for the | ||
| ``,`` option (:pep:`378`). | ||
|
|
||
| To date, there doesn't appear to be other widely-used languages or libraries | ||
| providing such a formatting option for negative zero. However, the same | ||
| ``z`` option syntax and semantics has been `proposed for C++ std::format()`_. | ||
| While the proposal was withdrawn for C++20, a consensus proposal is promised | ||
| for C++23. (The original `feature request`_ prompting this PEP was argued | ||
| without knowledge of the C++ proposal.) | ||
|
|
||
| .. _`proposed for C++ std::format()`: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1496r2.pdf | ||
| .. _`feature request`: https://bugs.python.org/issue45995 | ||
|
|
||
|
|
||
| Specification | ||
| ============= | ||
|
|
||
| An optional, literal ``z`` is added to the | ||
| `Format Specification Mini-Language`_ following ``sign``: | ||
|
|
||
| .. code-block:: text | ||
|
|
||
| [[fill]align][sign][z][#][0][width][grouping_option][.precision][type] | ||
|
|
||
| where ``z`` is allowed for numerical types other than integer. Support for | ||
| ``z`` is provided by the ``.__format__()`` method of each numeric type, | ||
| allowing the specifier to be used in f-strings, built-in ``format()``, and | ||
| ``str.format()``. The %-formatting style will not support the new option. | ||
|
|
||
| Synopsis: | ||
|
|
||
| .. code-block:: pycon | ||
|
|
||
| >>> x = -.00001 | ||
| >>> f'{x:z.1f}' | ||
| '0.0' | ||
|
|
||
| >>> x = decimal.Decimal('-.00001') | ||
| >>> '{:+z.1f}'.format(x) | ||
| '+0.0' | ||
|
|
||
| .. _`Format Specification Mini-Language`: https://docs.python.org/3/library/string.html#format-specification-mini-language | ||
|
|
||
|
|
||
| Backwards Compatibility | ||
| ======================= | ||
|
|
||
| The new formatting behavior is opt-in, so numerical formatting of existing | ||
| programs will not be affected. | ||
|
|
||
|
|
||
| Reference Implementation | ||
| ======================== | ||
|
|
||
| A reference implementation exists at `pull request #30049`_. | ||
|
|
||
| .. _`pull request #30049`: https://github.com/python/cpython/pull/30049 | ||
|
|
||
|
|
||
| Copyright | ||
| ========= | ||
|
|
||
| This document is placed in the public domain or under the | ||
| CC0-1.0-Universal license, whichever is more permissive. | ||
|
|
||
|
|
||
|
|
||
| .. | ||
| Local Variables: | ||
| mode: indented-text | ||
| indent-tabs-mode: nil | ||
| sentence-end-double-space: t | ||
| fill-column: 70 | ||
| coding: utf-8 | ||
| End: | ||

Uh oh!
There was an error while loading. Please reload this page.