225 lines
8.3 KiB
ReStructuredText
225 lines
8.3 KiB
ReStructuredText
PEP: 682
|
||
Title: Format Specifier for Signed Zero
|
||
Author: John Belmonte <john@neggie.net>
|
||
Sponsor: Mark Dickinson <dickinsm@gmail.com>
|
||
PEP-Delegate: Mark Dickinson
|
||
Discussions-To: https://discuss.python.org/t/pep-682-format-specifier-for-signed-zero/13596
|
||
Status: Final
|
||
Type: Standards Track
|
||
Content-Type: text/x-rst
|
||
Created: 29-Jan-2022
|
||
Python-Version: 3.11
|
||
Post-History: 08-Feb-2022
|
||
Resolution: https://discuss.python.org/t/accepting-pep-682-format-specifier-for-signed-zero/14088
|
||
|
||
|
||
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::
|
||
|
||
>>> 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::
|
||
|
||
>>> 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::
|
||
|
||
>>> 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::
|
||
|
||
>>> 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 any other widely-used language or library
|
||
providing a formatting option for negative zero. However, the same ``z``
|
||
option syntax and semantics specified below have 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.)
|
||
|
||
When Rust developers debated whether to suppress negative zero in ``print``
|
||
output, they took a small `survey of other languages`_. Notably, it didn't
|
||
mention any language providing an option for negative zero handling.
|
||
|
||
.. _`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
|
||
.. _`survey of other languages`: https://github.com/rust-lang/rfcs/issues/1074#issuecomment-718243936
|
||
|
||
|
||
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 floating-point presentation types (``f``, ``g``,
|
||
etc., as defined by the format specification documentation). 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()``.
|
||
|
||
When ``z`` is present, negative zero (whether the original value or the
|
||
result of rounding) will be normalized to positive zero.
|
||
|
||
Synopsis::
|
||
|
||
>>> 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`).
|
||
|
||
C99 ``printf`` already uses the ``z`` option character for another
|
||
purpose: qualifying the unsigned type (``u``) to match the length of
|
||
``size_t``. However, since the signed zero option specifically disallows
|
||
``z`` for integer presentation types, it's possible to disambiguate the two
|
||
uses, should C want to adopt this new option.
|
||
|
||
|
||
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:
|