python-peps/pep-0682/index.html

309 lines
24 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark">
<title>PEP 682 Format Specifier for Signed Zero | peps.python.org</title>
<link rel="shortcut icon" href="../_static/py.png">
<link rel="canonical" href="https://peps.python.org/pep-0682/">
<link rel="stylesheet" href="../_static/style.css" type="text/css">
<link rel="stylesheet" href="../_static/mq.css" type="text/css">
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" media="(prefers-color-scheme: light)" id="pyg-light">
<link rel="stylesheet" href="../_static/pygments_dark.css" type="text/css" media="(prefers-color-scheme: dark)" id="pyg-dark">
<link rel="alternate" type="application/rss+xml" title="Latest PEPs" href="https://peps.python.org/peps.rss">
<meta property="og:title" content='PEP 682 Format Specifier for Signed Zero | peps.python.org'>
<meta property="og:description" content="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 ...">
<meta property="og:type" content="website">
<meta property="og:url" content="https://peps.python.org/pep-0682/">
<meta property="og:site_name" content="Python Enhancement Proposals (PEPs)">
<meta property="og:image" content="https://peps.python.org/_static/og-image.png">
<meta property="og:image:alt" content="Python PEPs">
<meta property="og:image:width" content="200">
<meta property="og:image:height" content="200">
<meta name="description" content="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 ...">
<meta name="theme-color" content="#3776ab">
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="svg-sun-half" viewBox="0 0 24 24" pointer-events="all">
<title>Following system colour scheme</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="9"></circle>
<path d="M12 3v18m0-12l4.65-4.65M12 14.3l7.37-7.37M12 19.6l8.85-8.85"></path>
</svg>
</symbol>
<symbol id="svg-moon" viewBox="0 0 24 24" pointer-events="all">
<title>Selected dark colour scheme</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z"></path>
</svg>
</symbol>
<symbol id="svg-sun" viewBox="0 0 24 24" pointer-events="all">
<title>Selected light colour scheme</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
</symbol>
</svg>
<script>
document.documentElement.dataset.colour_scheme = localStorage.getItem("colour_scheme") || "auto"
</script>
<section id="pep-page-section">
<header>
<h1>Python Enhancement Proposals</h1>
<ul class="breadcrumbs">
<li><a href="https://www.python.org/" title="The Python Programming Language">Python</a> &raquo; </li>
<li><a href="../pep-0000/">PEP Index</a> &raquo; </li>
<li>PEP 682</li>
</ul>
<button id="colour-scheme-cycler" onClick="setColourScheme(nextColourScheme())">
<svg aria-hidden="true" class="colour-scheme-icon-when-auto"><use href="#svg-sun-half"></use></svg>
<svg aria-hidden="true" class="colour-scheme-icon-when-dark"><use href="#svg-moon"></use></svg>
<svg aria-hidden="true" class="colour-scheme-icon-when-light"><use href="#svg-sun"></use></svg>
<span class="visually-hidden">Toggle light / dark / auto colour theme</span>
</button>
</header>
<article>
<section id="pep-content">
<h1 class="page-title">PEP 682 Format Specifier for Signed Zero</h1>
<dl class="rfc2822 field-list simple">
<dt class="field-odd">Author<span class="colon">:</span></dt>
<dd class="field-odd">John Belmonte &lt;john&#32;&#97;t&#32;neggie.net&gt;</dd>
<dt class="field-even">Sponsor<span class="colon">:</span></dt>
<dd class="field-even">Mark Dickinson &lt;dickinsm&#32;&#97;t&#32;gmail.com&gt;</dd>
<dt class="field-odd">PEP-Delegate<span class="colon">:</span></dt>
<dd class="field-odd">Mark Dickinson</dd>
<dt class="field-even">Discussions-To<span class="colon">:</span></dt>
<dd class="field-even"><a class="reference external" href="https://discuss.python.org/t/pep-682-format-specifier-for-signed-zero/13596">Discourse thread</a></dd>
<dt class="field-odd">Status<span class="colon">:</span></dt>
<dd class="field-odd"><abbr title="Accepted and implementation complete, or no longer active">Final</abbr></dd>
<dt class="field-even">Type<span class="colon">:</span></dt>
<dd class="field-even"><abbr title="Normative PEP with a new feature for Python, implementation change for CPython or interoperability standard for the ecosystem">Standards Track</abbr></dd>
<dt class="field-odd">Created<span class="colon">:</span></dt>
<dd class="field-odd">29-Jan-2022</dd>
<dt class="field-even">Python-Version<span class="colon">:</span></dt>
<dd class="field-even">3.11</dd>
<dt class="field-odd">Post-History<span class="colon">:</span></dt>
<dd class="field-odd">08-Feb-2022</dd>
<dt class="field-even">Resolution<span class="colon">:</span></dt>
<dd class="field-even"><a class="reference external" href="https://discuss.python.org/t/accepting-pep-682-format-specifier-for-signed-zero/14088">Discourse thread</a></dd>
</dl>
<hr class="docutils" />
<section id="contents">
<details><summary>Table of Contents</summary><ul class="simple">
<li><a class="reference internal" href="#abstract">Abstract</a></li>
<li><a class="reference internal" href="#motivation">Motivation</a></li>
<li><a class="reference internal" href="#rationale">Rationale</a></li>
<li><a class="reference internal" href="#specification">Specification</a><ul>
<li><a class="reference internal" href="#design-notes">Design Notes</a></li>
</ul>
</li>
<li><a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a></li>
<li><a class="reference internal" href="#how-to-teach-this">How to Teach This</a></li>
<li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li>
<li><a class="reference internal" href="#copyright">Copyright</a></li>
</ul>
</details></section>
<section id="abstract">
<h2><a class="toc-backref" href="#abstract" role="doc-backlink">Abstract</a></h2>
<p>Though <code class="docutils literal notranslate"><span class="pre">float</span></code> and <code class="docutils literal notranslate"><span class="pre">Decimal</span></code> types can represent <a class="reference external" href="https://en.wikipedia.org/wiki/Signed_zero">signed zero</a>, 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 <a class="reference external" href="https://docs.python.org/3/library/string.html#formatstrings">string format specification</a> allowing negative
zero to be normalized to positive zero.</p>
</section>
<section id="motivation">
<h2><a class="toc-backref" href="#motivation" role="doc-backlink">Motivation</a></h2>
<p>Here is negative zero:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="n">x</span> <span class="o">=</span> <span class="o">-</span><span class="mf">0.</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">x</span>
<span class="go">-0.0</span>
</pre></div>
</div>
<p>When formatting a number, negative zero can result from rounding. Assuming
the users intention is truly to discard precision, the distinction between
negative and positive zero of the rounded result might be considered an
unwanted artifact:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="p">(</span><span class="mf">.002</span><span class="p">,</span> <span class="o">-</span><span class="mf">.001</span><span class="p">,</span> <span class="mf">.060</span><span class="p">):</span>
<span class="gp">... </span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;</span><span class="si">{</span><span class="n">x</span><span class="si">:</span><span class="s1"> .1f</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
<span class="go"> 0.0</span>
<span class="go">-0.0</span>
<span class="go"> 0.1</span>
</pre></div>
</div>
<p>There are various approaches to clearing the sign of a negative zero. It
can be achieved without a conditional by adding positive zero:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="n">x</span> <span class="o">=</span> <span class="o">-</span><span class="mf">0.</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">x</span> <span class="o">+</span> <span class="mf">0.</span>
<span class="go">0.0</span>
</pre></div>
</div>
<p>To normalize negative zero when formatting, it is necessary to perform
a redundant (and error-prone) pre-rounding of the input:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="p">(</span><span class="mf">.002</span><span class="p">,</span> <span class="o">-</span><span class="mf">.001</span><span class="p">,</span> <span class="mf">.060</span><span class="p">):</span>
<span class="gp">... </span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;</span><span class="si">{</span><span class="nb">round</span><span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mf">0.</span><span class="si">:</span><span class="s1"> .1f</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
<span class="go"> 0.0</span>
<span class="go"> 0.0</span>
<span class="go"> 0.1</span>
</pre></div>
</div>
<p>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:</p>
<ul class="simple">
<li><a class="reference external" href="https://stackoverflow.com/questions/11010683/how-to-have-negative-zero-always-formatted-as-positive-zero-in-a-python-string/36604981#36604981">How to have negative zero always formatted as positive zero in a
python string?</a> (Python, post-regex)</li>
<li><a class="reference external" href="https://stackoverflow.com/questions/41564311/ironpython-formatting-issue-with-modulo-operator-negative-zero/41564834#41564834">(Iron)Python formatting issue with modulo operator &amp; “negative zero”</a>
(Python, pre-round)</li>
<li><a class="reference external" href="https://stackoverflow.com/questions/11929096/negative-sign-in-case-of-zero-in-java">Negative sign in case of zero in java</a> (Java, post-regex)</li>
<li><a class="reference external" href="https://stackoverflow.com/questions/10969399/prevent-small-negative-numbers-printing-as-0">Prevent small negative numbers printing as “-0”</a> (Objective-C, custom
number formatter)</li>
</ul>
<p>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.</p>
</section>
<section id="rationale">
<h2><a class="toc-backref" href="#rationale" role="doc-backlink">Rationale</a></h2>
<p>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.</p>
<p>While it is possible to pre-round and normalize a number before formatting,
its tedious and prone to error if the rounding doesnt 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:</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">format_vector</span><span class="p">(</span><span class="n">v</span><span class="p">,</span> <span class="n">format_spec</span><span class="o">=</span><span class="s1">&#39;8.2f&#39;</span><span class="p">):</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;Format a vector (any iterable) using given per-term format string.&quot;&quot;&quot;</span>
<span class="k">return</span> <span class="sa">f</span><span class="s2">&quot;[</span><span class="si">{</span><span class="s1">&#39;,&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;</span><span class="si">{</span><span class="n">term</span><span class="si">:{</span><span class="n">format_spec</span><span class="si">}}</span><span class="s1">&#39;</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">term</span><span class="w"> </span><span class="ow">in</span><span class="w"> </span><span class="n">v</span><span class="p">)</span><span class="si">}</span><span class="s2">]&quot;</span>
</pre></div>
</div>
<p>To date, there doesnt appear to be any other widely-used language or library
providing a formatting option for negative zero. However, the same <code class="docutils literal notranslate"><span class="pre">z</span></code>
option syntax and semantics specified below have been <a class="reference external" href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1496r2.pdf">proposed for C++
std::format()</a>. While the proposal was withdrawn for C++20, a consensus
proposal is promised for C++23. (The original <a class="reference external" href="https://bugs.python.org/issue45995">feature request</a> prompting
this PEP was argued without knowledge of the C++ proposal.)</p>
<p>When Rust developers debated whether to suppress negative zero in <code class="docutils literal notranslate"><span class="pre">print</span></code>
output, they took a small <a class="reference external" href="https://github.com/rust-lang/rfcs/issues/1074#issuecomment-718243936">survey of other languages</a>. Notably, it didnt
mention any language providing an option for negative zero handling.</p>
</section>
<section id="specification">
<h2><a class="toc-backref" href="#specification" role="doc-backlink">Specification</a></h2>
<p>An optional, literal <code class="docutils literal notranslate"><span class="pre">z</span></code> is added to the
<a class="reference external" href="https://docs.python.org/3/library/string.html#format-specification-mini-language">Format Specification Mini-Language</a> following <code class="docutils literal notranslate"><span class="pre">sign</span></code>:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>[[fill]align][sign][z][#][0][width][grouping_option][.precision][type]
</pre></div>
</div>
<p>where <code class="docutils literal notranslate"><span class="pre">z</span></code> is allowed for floating-point presentation types (<code class="docutils literal notranslate"><span class="pre">f</span></code>, <code class="docutils literal notranslate"><span class="pre">g</span></code>,
etc., as defined by the format specification documentation). Support for
<code class="docutils literal notranslate"><span class="pre">z</span></code> is provided by the <code class="docutils literal notranslate"><span class="pre">.__format__()</span></code> method of each numeric type,
allowing the specifier to be used in f-strings, built-in <code class="docutils literal notranslate"><span class="pre">format()</span></code>, and
<code class="docutils literal notranslate"><span class="pre">str.format()</span></code>.</p>
<p>When <code class="docutils literal notranslate"><span class="pre">z</span></code> is present, negative zero (whether the original value or the
result of rounding) will be normalized to positive zero.</p>
<p>Synopsis:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="n">x</span> <span class="o">=</span> <span class="o">-</span><span class="mf">.00001</span>
<span class="gp">&gt;&gt;&gt; </span><span class="sa">f</span><span class="s1">&#39;</span><span class="si">{</span><span class="n">x</span><span class="si">:</span><span class="s1">z.1f</span><span class="si">}</span><span class="s1">&#39;</span>
<span class="go">&#39;0.0&#39;</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">x</span> <span class="o">=</span> <span class="n">decimal</span><span class="o">.</span><span class="n">Decimal</span><span class="p">(</span><span class="s1">&#39;-.00001&#39;</span><span class="p">)</span>
<span class="gp">&gt;&gt;&gt; </span><span class="s1">&#39;{:+z.1f}&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
<span class="go">&#39;+0.0&#39;</span>
</pre></div>
</div>
<section id="design-notes">
<h3><a class="toc-backref" href="#design-notes" role="doc-backlink">Design Notes</a></h3>
<p>The solution must be opt-in, because we cant change the behavior of
programs that may be expecting or relying on negative zero when formatting
numbers.</p>
<p>The proposed extension is intentionally <code class="docutils literal notranslate"><span class="pre">[sign][z]</span></code> rather than
<code class="docutils literal notranslate"><span class="pre">[sign[z]]</span></code>. The default for <code class="docutils literal notranslate"><span class="pre">sign</span></code> (<code class="docutils literal notranslate"><span class="pre">-</span></code>) is not widely known or
explicitly written, so this avoids everyone having to learn it just to use
the <code class="docutils literal notranslate"><span class="pre">z</span></code> option.</p>
<p>While f-strings, built-in <code class="docutils literal notranslate"><span class="pre">format()</span></code>, and <code class="docutils literal notranslate"><span class="pre">str.format()</span></code> can access
the new option, %-formatting cannot. There is already precedent for not
extending %-formatting with new options, as was the case for the
<code class="docutils literal notranslate"><span class="pre">,</span></code> option (<a class="pep reference internal" href="../pep-0378/" title="PEP 378 Format Specifier for Thousands Separator">PEP 378</a>).</p>
<p>C99 <code class="docutils literal notranslate"><span class="pre">printf</span></code> already uses the <code class="docutils literal notranslate"><span class="pre">z</span></code> option character for another
purpose: qualifying the unsigned type (<code class="docutils literal notranslate"><span class="pre">u</span></code>) to match the length of
<code class="docutils literal notranslate"><span class="pre">size_t</span></code>. However, since the signed zero option specifically disallows
<code class="docutils literal notranslate"><span class="pre">z</span></code> for integer presentation types, its possible to disambiguate the two
uses, should C want to adopt this new option.</p>
</section>
</section>
<section id="backwards-compatibility">
<h2><a class="toc-backref" href="#backwards-compatibility" role="doc-backlink">Backwards Compatibility</a></h2>
<p>The new formatting behavior is opt-in, so numerical formatting of existing
programs will not be affected.</p>
</section>
<section id="how-to-teach-this">
<h2><a class="toc-backref" href="#how-to-teach-this" role="doc-backlink">How to Teach This</a></h2>
<p>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 <code class="docutils literal notranslate"><span class="pre">z</span></code> option on a negative
value thats rounded to zero by the formatting should be enough. For an
independent developer encountering the feature in someone elses code,
reference to the <a class="reference external" href="https://docs.python.org/3/library/string.html#format-specification-mini-language">Format Specification Mini-Language</a> section of the
library reference manual should suffice.</p>
</section>
<section id="reference-implementation">
<h2><a class="toc-backref" href="#reference-implementation" role="doc-backlink">Reference Implementation</a></h2>
<p>A reference implementation exists at <a class="reference external" href="https://github.com/python/cpython/pull/30049">pull request #30049</a>.</p>
</section>
<section id="copyright">
<h2><a class="toc-backref" href="#copyright" role="doc-backlink">Copyright</a></h2>
<p>This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.</p>
</section>
</section>
<hr class="docutils" />
<p>Source: <a class="reference external" href="https://github.com/python/peps/blob/main/peps/pep-0682.rst">https://github.com/python/peps/blob/main/peps/pep-0682.rst</a></p>
<p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0682.rst">2023-09-09 17:39:29 GMT</a></p>
</article>
<nav id="pep-sidebar">
<h2>Contents</h2>
<ul>
<li><a class="reference internal" href="#abstract">Abstract</a></li>
<li><a class="reference internal" href="#motivation">Motivation</a></li>
<li><a class="reference internal" href="#rationale">Rationale</a></li>
<li><a class="reference internal" href="#specification">Specification</a><ul>
<li><a class="reference internal" href="#design-notes">Design Notes</a></li>
</ul>
</li>
<li><a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a></li>
<li><a class="reference internal" href="#how-to-teach-this">How to Teach This</a></li>
<li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li>
<li><a class="reference internal" href="#copyright">Copyright</a></li>
</ul>
<br>
<a id="source" href="https://github.com/python/peps/blob/main/peps/pep-0682.rst">Page Source (GitHub)</a>
</nav>
</section>
<script src="../_static/colour_scheme.js"></script>
<script src="../_static/wrap_tables.js"></script>
<script src="../_static/sticky_banner.js"></script>
</body>
</html>