Skip to content
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

PEP 682: Format Specifier for Signed Zero #2295

Merged
merged 13 commits into from
Feb 8, 2022
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -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
# ...
Expand Down
217 changes: 217 additions & 0 deletions pep-0682.rst
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
Comment on lines +63 to +69
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, when code-block:: pycon is used, I noticed that the official rendering applies a proportional font to >>> and ... specifically, which looks rather odd and affects indent alignment.

Screen Shot 2022-02-09 at 8 35 43 AM

Copy link
Member

Choose a reason for hiding this comment

The 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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grep shows only one other PEP using code-block:: pycon, so there's no safety in numbers. I'll remove the usage for now, as part of #2317.

Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Member

@AA-Turner AA-Turner Feb 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would it be "pycon", and not "python"

pycon is the Pygments lexer for python console text, versus python which is for scripts. Semantically pycon is correct here, although I don't know the exact difference between the two.

It seems like all of the other PEPs don't specify a language

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

Copy link
Member

@CAM-Gerlach CAM-Gerlach Feb 15, 2022

Choose a reason for hiding this comment

The 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 pycon is problematic, then the correct approach here is to just use python instead, not remove this entirely.

Copy link
Contributor Author

@belm0 belm0 Feb 15, 2022

Choose a reason for hiding this comment

The 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)):

  • the console-specific formatting looks great with the new rendering system for peps.python.org
  • however, rendering on the standard pep site is botched. @AA-Turner notes it may be hard to fix. (I'd be happy to help if it's feasible.)
  • there is only one other PEP so far using pycon
  • it seems we'll need a script to convert console blocks in existing PEP's anyway, so perhaps no need to put this PEP on the bleeding edge

Copy link
Member

@CAM-Gerlach CAM-Gerlach Feb 15, 2022

Choose a reason for hiding this comment

The 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 pycon highlighter and not just simply using explicit .. code-block markup which many PEPs are now using; simply using the python highlighter instead should fix the issue (unless you've confirmed it doesn't, but I didn't find any mention of that here nor there). And I am not aware of plans for batch-converting all old PEPs to use the new explicit language markup, except in cases on active/process PEPs where the highlighting is widely off (wrong language, etc) but there's no reason not to be explicit and correct in new ones as @AA-Turner says, unless it is not compatible with both renderers.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

batch-converting all old PEPs to use the new explicit language markup

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 pycon here on a retroactive sweep.

typefaces

another debate for another time ;)

A

Copy link
Member

Choose a reason for hiding this comment

The 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.

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 .. code-block:: python syntax for all code blocks, which I believe is your viewpoint as well.


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: