Sitelet https://github.com/python/cpython/issues/90153
Skip to content

string formatting: normalize negative zero #90153

Description

@belm0
mannequin
BPO 45995
Nosy @mdickinson, @belm0, @ericvsmith, @stevendaprano, @serhiy-storchaka, @belm0
PRs
  • bpo-45995: add "z" format specifer to coerce negative 0 to zero #30049
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = 'https://github.com/mdickinson'
    closed_at = None
    created_at = <Date 2021-12-06.12:03:46.943>
    labels = ['type-feature', 'library', '3.11']
    title = 'string formatting: normalize negative zero'
    updated_at = <Date 2022-03-13.12:04:51.159>
    user = 'https://github.com/belm0'

    bugs.python.org fields:

    activity = <Date 2022-03-13.12:04:51.159>
    actor = 'mark.dickinson'
    assignee = 'mark.dickinson'
    closed = False
    closed_date = None
    closer = None
    components = ['Library (Lib)']
    creation = <Date 2021-12-06.12:03:46.943>
    creator = 'John Belmonte'
    dependencies = []
    files = []
    hgrepos = []
    issue_num = 45995
    keywords = ['patch']
    message_count = 23.0
    messages = ['407792', '407793', '407796', '407810', '407822', '407857', '407905', '407928', '407929', '407930', '407934', '407973', '407990', '408367', '408413', '408529', '408800', '410838', '411243', '411295', '411302', '412360', '415034']
    nosy_count = 6.0
    nosy_names = ['mark.dickinson', 'jbelmonte', 'eric.smith', 'steven.daprano', 'serhiy.storchaka', 'John Belmonte']
    pr_nums = ['30049']
    priority = 'normal'
    resolution = None
    stage = 'patch review'
    status = 'open'
    superseder = None
    type = 'enhancement'
    url = 'https://bugs.python.org/issue45995'
    versions = ['Python 3.11']

    Activity

    1. belm0 commented on Dec 6, 2021

      belm0mannequin
      MannequinAuthor

      proposal: add a string formatting option to normalize negative 0 values to 0

      use case: rounded display of a float that is nominally 0, where the distraction of a flashing minus sign from minute changes around 0 is unwanted

      example:
      >>> '%~5.1f' % -.00001
      '  0.0'

      format spec before:
      format_spec ::= [[fill]align][sign][#][0][width][grouping_option][.precision][type]
      after:
      format_spec ::= [[fill]align][sign][~][#][0][width][grouping_option][.precision][type]
      where '~' is only allowed for number types

      implementation: if '~' is present in the spec, add 0 to the value after applying precision

    2. added
      stdlibStandard Library Python modules in the Lib/ directory
      type-featureA feature request or enhancement
      on Dec 6, 2021
    3. serhiy-storchaka commented on Dec 6, 2021

      @serhiy-storchaka
      Member

      To normalize negative 0.0 to 0.0 you can just add 0.0. It will work with any method of converting floats to string, there is no need to change all formatting specifications.

      >>> x = -0.0
      >>> x
      -0.0
      >>> x + 0.0
      0.0
    4. belm0 commented on Dec 6, 2021

      belm0mannequin
      MannequinAuthor

      To normalize negative 0.0 to 0.0 you can just add 0.0.

      yes, I'm aware-- see "implementation" in the original post

      there is no need to change all formatting specifications

      For adding 0 to work, it must be done after the rounding. That means if you want make use of anything in the current formatting spec regarding precision, normalizing negative zero would need to be a proper option of the formatting spec.

    5. stevendaprano commented on Dec 6, 2021

      @stevendaprano
      Member

      It was decided long ago that % formatting would not be enhanced with new features. I think that it is supposed to match the standard C formatting codes, and nothing else.

      So this is unlikely to be approved for % formatting.

      It *might* be approved for the format method and f-strings. (I suspect that unless you get immediate and uncontroversial agreement from multiple core developers, you may need to take it for further discussion and perhaps even a PEP.)

      What you call a "distraction" I consider to be critical part of the display. The numbers you are displaying actually are negative, and rounding them for display does not change that. So they ought to show the minus sign. So I'm not really very sympathetic to this feature request.

    6. belm0 commented on Dec 6, 2021

      belm0mannequin
      MannequinAuthor

      Here is the same proposal made for C++ std::format:

      http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1496r2.pdf

      It makes fair arguments for the feature's use, and explains why the problem is hard to work around.

      It was withdrawn by the author for C++20, but a consensus proposal is promised for C++23.

    7. mdickinson commented on Dec 6, 2021

      @mdickinson
      Member

      I'd support having this functionality available for format and for f-strings. (As Steven says, changing %-formatting doesn't seem viable.) It really is awkward to do this in any other way, and I'm reliably informed that normal people don't expect to see negative zeros in formatted numeric output.

      It did take me a few minutes to get my head around the idea that f"{-0.01:+.1f}" would return "+0.0" rather than "-0.0" or " 0.0" or just plain "0.0" under this proposal, but I agree that it seems like the only thing that can be consistent and make sense.

      I'm not 100% convinced by the particular spelling proposed, but I don't have anything better to suggest. If C++ might be going with a "z", would it make sense to do the same for Python?

      I don't forsee any implementation difficulties for float and complex types. For Decimal, we'd need to "own" the string formatting, taking that responsibility away from mpdecimal, but there are already other reasons to do that. Once we've done that, again the implementation doesn't seem onerous.

    8. serhiy-storchaka commented on Dec 7, 2021

      @serhiy-storchaka
      Member

      Well, it makes sense for negative zero produced by rounding.

      But if we add a special support for this case, it would be useful to have some control on the type of rounding. Currently floats are rounded to the nearest decimal number, but in some cases it would be better to round up, down, toward zero or infinity (seed for example bpo-44884).

      You can round explicitly before formatting, but this solution is also applicable for this issue:

      >>> '%5.1f' % (round(-.00001, 1) + 0.0)
      '  0.0'
    9. belm0 commented on Dec 7, 2021

      belm0mannequin
      MannequinAuthor

      changing %-formatting doesn't seem viable

      I'm concerned about treating %-formatting specially. As far as float/complex, the logical and efficient place to put this change seems to be PyOS_double_to_string(), which affects all three formatting options.

      For example, the dtoa case is as simple as this change to format_float_short():

      /* coerce negative zero to positive */
      if (sign == 1 && ((digits_len == 0 && decpt == -1) ||
                        (digits_len == 1 && digits[0] == '0'))) {
          sign = 0;
      }
      
    10. stevendaprano commented on Dec 7, 2021

      @stevendaprano
      Member

      Sorry John, I don't understand your comment about "treating %-formatting
      specifically". Isn't the point here not to change %-formatting at all?

    11. 10 remaining items

    12. belm0 commented on Jan 18, 2022

      belm0mannequin
      MannequinAuthor

      Mark, would you give it a review this month? (PR has been marked stale.)

    13. mdickinson commented on Jan 22, 2022

      @mdickinson
      Member

      [John]

      Mark, would you give it a review this month?

      Apologies; my holiday-break free time was nobbled from unexpected quarters. I can't promise to find time this month, but I can promise to try. I did at least skim through the PR, and while there are still likely some iterations needed I'm satisfied that this is technically feasible.

      But I'm afraid that's the easy part. If this is to go in, the other problem we still have to solve is achieving some consensus among the core developers that this is worth doing. Right now, judging by comments on this issue, I think I'm the only core dev who thinks this is a good idea; others are lukewarm at best, and I'm not willing to unilaterally approve and merge these changes without something closer to a consensus. There are a couple of ways forward here:

      • Post the proposal on python-ideas to get wider visibility and feedback. If everyone agrees this is a great idea (from experience, this seems an unlikely outcome), then we can go ahead and merge. Otherwise we'd likely need a PEP to move forward.

      • Bypass the python-ideas step, write the PEP, discuss in the appropriate forums, and then submit to the SC for approval / rejection.

      • Convince Eric Smith. :-) With apologies to Eric for singling him out: Eric could reasonably be described as the steward/maintainer of the formatting machinery, so if he's persuaded, that's good enough for me.

      The fact that you've already created a working implementation so that people can experiment is a bonus when it comes to trying to sell this to others.

      I don't have the bandwidth to write a PEP, but I would be happy to act as PEP sponsor.

    14. ericvsmith commented on Jan 22, 2022

      @ericvsmith
      Member

      Wow, thanks, Mark!

      I'm generally in favor. The selling points to me are that it needs to happen post-rounding, and the C++ discussion. It would be better if this were already accepted in C++. I'll note that the paper is proposing a 'z' modifier to the sign, so I guess for us that would translate to: [sign[optional-z]] instead of just sign. I'd have to noodle through the differences between that the proposed [sign][~]. I guess this would all be worked out in a PEP.

      My only reservation is Mark's comment: """For Decimal, we'd need to "own" the string formatting, taking that responsibility away from mpdecimal, but there are already other reasons to do that."""

      If Mark is okay with that (right back at you, Mark!), then I think a PEP is the next step. It doesn't need to be huge, sort of like PEP-378.

    15. belm0 commented on Jan 22, 2022

      belm0mannequin
      MannequinAuthor

      Thank you Mark and Eric.

      I'll note that the paper is proposing a 'z' modifier to the sign, so I guess for us that would translate to: [sign[optional-z]] instead of just sign. I'd have to noodle through the differences between that the proposed [sign][~].

      The C++ paper proposes [sign][z] (i.e. you can have the z alone without an explicit +/-), and this is what I implemented in the Python PR. My original proposal with tilde was discarded.

      My only reservation is Mark's comment: """For Decimal, we'd need to "own" the string formatting, taking that responsibility away from mpdecimal, but there are already other reasons to do that."""

      In the PR I was able to avoid taking that on by preprocessing the format string before handing it to mpdecimal. The code was already doing such things to handle the NULL fill character.

    16. belm0 commented on Feb 2, 2022

      belm0mannequin
      MannequinAuthor
    17. mdickinson commented on Mar 13, 2022

      @mdickinson
      Member

      I forgot to update here:

      PEP at python/peps#2295

      For the record, PEP-682 has been accepted.

    18. transferred this issue fromon Apr 10, 2022
    19. added a commit that references this issue on Apr 11, 2022
    20. mdickinson commented on Apr 11, 2022

      @mdickinson
      Member

      PR #30049 has been merged!

    21. added a commit that references this issue on Jun 11, 2022
    22. added 2 commits that reference this issue on Jun 11, 2022
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    Labels

    3.11only security fixesstdlibStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancement

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions