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>
This commit is contained in:
parent
f2c28db177
commit
6d66078352
|
@ -558,6 +558,7 @@ pep-0678.rst @iritkatriel
|
|||
pep-0679.rst @pablogsal
|
||||
pep-0680.rst @encukou
|
||||
pep-0681.rst @jellezijlstra
|
||||
pep-0682.rst @mdickinson
|
||||
# ...
|
||||
# pep-0754.txt
|
||||
# ...
|
||||
|
|
|
@ -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:
|
Loading…
Reference in New Issue