-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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 all 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,217 @@ | ||
PEP: 682 | ||
Title: Format Specifier for Signed Zero | ||
Author: John Belmonte <[email protected]> | ||
Sponsor: Mark Dickinson <[email protected]> | ||
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
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. 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 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 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. 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 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 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)):
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 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 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 |
||
|
||
There is ample evidence that, regardless of the language, programmers are | ||
often looking for a way to suppress negative zero, and landing on a | ||
variety of workarounds (pre-round, post-regex, etc.). A sampling: | ||
|
||
* `How to have negative zero always formatted as positive zero in a | ||
python string?`_ (Python, post-regex) | ||
* `(Iron)Python formatting issue with modulo operator & "negative zero"`_ | ||
(Python, pre-round) | ||
* `Negative sign in case of zero in java`_ (Java, post-regex) | ||
* `Prevent small negative numbers printing as "-0"`_ (Objective-C, custom | ||
number formatter) | ||
|
||
What we would like instead is a first-class option to normalize negative | ||
mdickinson marked this conversation as resolved.
Show resolved
Hide resolved
|
||
zero, on top of everything else that numerical string formatting already | ||
offers. | ||
belm0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
.. _`How to have negative zero always formatted as positive zero in a python string?`: https://stackoverflow.com/questions/11010683/how-to-have-negative-zero-always-formatted-as-positive-zero-in-a-python-string/36604981#36604981 | ||
.. _`(Iron)Python formatting issue with modulo operator & "negative zero"`: https://stackoverflow.com/questions/41564311/ironpython-formatting-issue-with-modulo-operator-negative-zero/41564834#41564834 | ||
.. _`Negative sign in case of zero in java`: https://stackoverflow.com/questions/11929096/negative-sign-in-case-of-zero-in-java | ||
.. _`Prevent small negative numbers printing as "-0"`: https://stackoverflow.com/questions/10969399/prevent-small-negative-numbers-printing-as-0 | ||
|
||
|
||
Rationale | ||
========= | ||
|
||
There are use cases where negative zero is unwanted in formatted number | ||
output -- arguably, not wanting it is more common. Expanding the format | ||
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)}]" | ||
|
||
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 | ||
|
||
|
||
Design Notes | ||
------------ | ||
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. | ||
|
||
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`). | ||
|
||
|
||
Backwards Compatibility | ||
======================= | ||
|
||
The new formatting behavior is opt-in, so numerical formatting of existing | ||
programs will not be affected. | ||
|
||
|
||
How to Teach This | ||
================= | ||
A typical introductory Python course will not cover string formatting | ||
in full detail. For such a course, no adjustments would need to be made. | ||
For a course that does go into details of the string format specification, | ||
a single example demonstrating the effect of the `z` option on a negative | ||
value that's rounded to zero by the formatting should be enough. For an | ||
independent developer encountering the feature in someone else's code, | ||
reference to the `Format Specification Mini-Language`_ section of the | ||
library reference manual should suffice. | ||
|
||
.. _`Format Specification Mini-Language`: https://docs.python.org/3/library/string.html#format-specification-mini-language | ||
|
||
|
||
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.