Skip to content

Commit 6d66078

Browse files
belm0AA-TurnerCAM-GerlachhugovkJelleZijlstra
authored
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>
1 parent f2c28db commit 6d66078

File tree

2 files changed

+218
-0
lines changed

2 files changed

+218
-0
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,7 @@ pep-0678.rst @iritkatriel
558558
pep-0679.rst @pablogsal
559559
pep-0680.rst @encukou
560560
pep-0681.rst @jellezijlstra
561+
pep-0682.rst @mdickinson
561562
# ...
562563
# pep-0754.txt
563564
# ...

pep-0682.rst

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
PEP: 682
2+
Title: Format Specifier for Signed Zero
3+
Author: John Belmonte <john@neggie.net>
4+
Sponsor: Mark Dickinson <dickinsm@gmail.com>
5+
Discussions-To:
6+
Status: Draft
7+
Type: Standards Track
8+
Content-Type: text/x-rst
9+
Created: 29-Jan-2022
10+
Python-Version: 3.11
11+
Post-History:
12+
13+
14+
Abstract
15+
========
16+
17+
Though ``float`` and ``Decimal`` types can represent `signed zero`_, in many
18+
fields of mathematics negative zero is surprising or unwanted -- especially
19+
in the context of displaying an (often rounded) numerical result. This PEP
20+
proposes an extension to the `string format specification`_ allowing negative
21+
zero to be normalized to positive zero.
22+
23+
.. _`signed zero`: https://en.wikipedia.org/wiki/Signed_zero
24+
.. _`string format specification`: https://docs.python.org/3/library/string.html#formatstrings
25+
26+
27+
Motivation
28+
==========
29+
30+
Here is negative zero:
31+
32+
.. code-block:: pycon
33+
34+
>>> x = -0.
35+
>>> x
36+
-0.0
37+
38+
When formatting a number, negative zero can result from rounding. Assuming
39+
the user's intention is truly to discard precision, the distinction between
40+
negative and positive zero of the rounded result might be considered an
41+
unwanted artifact:
42+
43+
.. code-block:: pycon
44+
45+
>>> for x in (.002, -.001, .060):
46+
... print(f'{x: .1f}')
47+
0.0
48+
-0.0
49+
0.1
50+
51+
There are various approaches to clearing the sign of a negative zero. It
52+
can be achieved without a conditional by adding positive zero:
53+
54+
.. code-block:: pycon
55+
56+
>>> x = -0.
57+
>>> x + 0.
58+
0.0
59+
60+
To normalize negative zero when formatting, it is necessary to perform
61+
a redundant (and error-prone) pre-rounding of the input:
62+
63+
.. code-block:: pycon
64+
65+
>>> for x in (.002, -.001, .060):
66+
... print(f'{round(x, 1) + 0.: .1f}')
67+
0.0
68+
0.0
69+
0.1
70+
71+
There is ample evidence that, regardless of the language, programmers are
72+
often looking for a way to suppress negative zero, and landing on a
73+
variety of workarounds (pre-round, post-regex, etc.). A sampling:
74+
75+
* `How to have negative zero always formatted as positive zero in a
76+
python string?`_ (Python, post-regex)
77+
* `(Iron)Python formatting issue with modulo operator & "negative zero"`_
78+
(Python, pre-round)
79+
* `Negative sign in case of zero in java`_ (Java, post-regex)
80+
* `Prevent small negative numbers printing as "-0"`_ (Objective-C, custom
81+
number formatter)
82+
83+
What we would like instead is a first-class option to normalize negative
84+
zero, on top of everything else that numerical string formatting already
85+
offers.
86+
87+
.. _`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
88+
.. _`(Iron)Python formatting issue with modulo operator & "negative zero"`: https://stackoverflow.com/questions/41564311/ironpython-formatting-issue-with-modulo-operator-negative-zero/41564834#41564834
89+
.. _`Negative sign in case of zero in java`: https://stackoverflow.com/questions/11929096/negative-sign-in-case-of-zero-in-java
90+
.. _`Prevent small negative numbers printing as "-0"`: https://stackoverflow.com/questions/10969399/prevent-small-negative-numbers-printing-as-0
91+
92+
93+
Rationale
94+
=========
95+
96+
There are use cases where negative zero is unwanted in formatted number
97+
output -- arguably, not wanting it is more common. Expanding the format
98+
specification is the best way to support this because number formatting
99+
already incorporates rounding, and the normalization of negative zero must
100+
happen after rounding.
101+
102+
While it is possible to pre-round and normalize a number before formatting,
103+
it's tedious and prone to error if the rounding doesn't precisely match
104+
that of the format spec. Furthermore, functions that wrap formatting would
105+
find themselves having to parse format specs to extract the precision
106+
information. For example, consider how this utility for formatting
107+
one-dimensional numerical arrays would be complicated by such pre-rounding:
108+
109+
.. code-block:: python
110+
111+
def format_vector(v, format_spec='8.2f'):
112+
"""Format a vector (any iterable) using given per-term format string."""
113+
return f"[{','.join(f'{term:{format_spec}}' for term in v)}]"
114+
115+
To date, there doesn't appear to be other widely-used languages or libraries
116+
providing such a formatting option for negative zero. However, the same
117+
``z`` option syntax and semantics has been `proposed for C++ std::format()`_.
118+
While the proposal was withdrawn for C++20, a consensus proposal is promised
119+
for C++23. (The original `feature request`_ prompting this PEP was argued
120+
without knowledge of the C++ proposal.)
121+
122+
.. _`proposed for C++ std::format()`: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1496r2.pdf
123+
.. _`feature request`: https://bugs.python.org/issue45995
124+
125+
126+
Specification
127+
=============
128+
129+
An optional, literal ``z`` is added to the
130+
`Format Specification Mini-Language`_ following ``sign``:
131+
132+
.. code-block:: text
133+
134+
[[fill]align][sign][z][#][0][width][grouping_option][.precision][type]
135+
136+
where ``z`` is allowed for numerical types other than integer. Support for
137+
``z`` is provided by the ``.__format__()`` method of each numeric type,
138+
allowing the specifier to be used in f-strings, built-in ``format()``, and
139+
``str.format()``. The %-formatting style will not support the new option.
140+
141+
Synopsis:
142+
143+
.. code-block:: pycon
144+
145+
>>> x = -.00001
146+
>>> f'{x:z.1f}'
147+
'0.0'
148+
149+
>>> x = decimal.Decimal('-.00001')
150+
>>> '{:+z.1f}'.format(x)
151+
'+0.0'
152+
153+
.. _`Format Specification Mini-Language`: https://docs.python.org/3/library/string.html#format-specification-mini-language
154+
155+
156+
Design Notes
157+
------------
158+
The solution must be opt-in, because we can't change the behavior of
159+
programs that may be expecting or relying on negative zero when formatting
160+
numbers.
161+
162+
The proposed extension is intentionally ``[sign][z]`` rather than
163+
``[sign[z]]``. The default for ``sign`` (``-``) is not widely known or
164+
explicitly written, so this avoids everyone having to learn it just to use
165+
the ``z`` option.
166+
167+
While f-strings, built-in ``format()``, and ``str.format()`` can access
168+
the new option, %-formatting cannot. There is already precedent for not
169+
extending %-formatting with new options, as was the case for the
170+
``,`` option (:pep:`378`).
171+
172+
173+
Backwards Compatibility
174+
=======================
175+
176+
The new formatting behavior is opt-in, so numerical formatting of existing
177+
programs will not be affected.
178+
179+
180+
How to Teach This
181+
=================
182+
A typical introductory Python course will not cover string formatting
183+
in full detail. For such a course, no adjustments would need to be made.
184+
For a course that does go into details of the string format specification,
185+
a single example demonstrating the effect of the `z` option on a negative
186+
value that's rounded to zero by the formatting should be enough. For an
187+
independent developer encountering the feature in someone else's code,
188+
reference to the `Format Specification Mini-Language`_ section of the
189+
library reference manual should suffice.
190+
191+
.. _`Format Specification Mini-Language`: https://docs.python.org/3/library/string.html#format-specification-mini-language
192+
193+
194+
Reference Implementation
195+
========================
196+
197+
A reference implementation exists at `pull request #30049`_.
198+
199+
.. _`pull request #30049`: https://github.com/python/cpython/pull/30049
200+
201+
202+
Copyright
203+
=========
204+
205+
This document is placed in the public domain or under the
206+
CC0-1.0-Universal license, whichever is more permissive.
207+
208+
209+
210+
..
211+
Local Variables:
212+
mode: indented-text
213+
indent-tabs-mode: nil
214+
sentence-end-double-space: t
215+
fill-column: 70
216+
coding: utf-8
217+
End:

0 commit comments

Comments
 (0)