-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
PEP 682: Format Specifier for Signed Zero (#2295)
Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Co-authored-by: CAM Gerlach <CAM.Gerlach@Gerlach.CAM> Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com> Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
- Loading branch information
1 parent
f2c28db
commit 6d66078
Showing
2 changed files
with
218 additions
and
0 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 <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 | ||
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 | ||
zero, on top of everything else that numerical string formatting already | ||
offers. | ||
|
||
.. _`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: |