This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: str.format for fixed width float can return a string longer than the maximum specified
Type: behavior Stage: resolved
Components: Versions: Python 3.3
process
Status: closed Resolution: wont fix
Dependencies: Superseder:
Assigned To: eric.smith Nosy List: BreamoreBoy, aubmoon, eric.smith, mark.dickinson, ned.deily, skrah
Priority: normal Keywords:

Created on 2014-02-28 21:53 by aubmoon, last changed 2022-04-11 14:57 by admin. This issue is now closed.

Messages (14)
msg212478 - (view) Author: (aubmoon) Date: 2014-02-28 21:53
"{:+10.8}".format(0.12345678)

returns

'+0.12345678' (11 chars)

should return

'+.12345678' (10 chars)
msg212480 - (view) Author: Ned Deily (ned.deily) * (Python committer) Date: 2014-02-28 22:32
I think you are misunderstanding the meaning of the width component (e.g. the 10 in your example) of a format specification.  As described in the documentation, width is a decimal integer defining the *minimum* field width, not the *maximum* field width.  As necessary, the generated field will be as long as necessary to represent the item as requested by the format spec, but it will be at least "width" characters long.

http://docs.python.org/3.3/library/string.html#format-specification-mini-language
msg212483 - (view) Author: (aubmoon) Date: 2014-03-01 00:48
Admittedly maximum is not correct; however, minimum is not right either.
The 10 is the desired (or target) width. Fixed width prints are commonly
used with very legacy systems that die a painful death when fixed width
strings are parsed by index and a field has spilled over a column. The
point is that the format returns a string that is longer than the desired
length when a perfectly valid version meets both the format and desired
length (making it more correct). The desired length should only be exceeded
when no solutions can be found. Note the below statement parses without
error. For this reason I feel this is a valid but minor issue. By using a
regular expression or slicing the desired result can be achieved, but with
extra code.
Thanks for the quick response!

-Mark

>>> float('+.12345678')

On Friday, February 28, 2014, Ned Deily <report@bugs.python.org> wrote:

>
> Ned Deily added the comment:
>
> I think you are misunderstanding the meaning of the width component (e.g.
> the 10 in your example) of a format specification.  As described in the
> documentation, width is a decimal integer defining the *minimum* field
> width, not the *maximum* field width.  As necessary, the generated field
> will be as long as necessary to represent the item as requested by the
> format spec, but it will be at least "width" characters long.
>
>
> http://docs.python.org/3.3/library/string.html#format-specification-mini-language
>
> ----------
> nosy: +ned.deily
> resolution:  -> invalid
> stage:  -> committed/rejected
> status: open -> closed
>
> _______________________________________
> Python tracker <report@bugs.python.org <javascript:;>>
> <http://bugs.python.org/issue20811>
> _______________________________________
>
msg212484 - (view) Author: Ned Deily (ned.deily) * (Python committer) Date: 2014-03-01 02:48
OK, so the issue is that Python float literals in the range 0.0 < x < 1.0 can be spelled without a "0" before the decimal point but the built-in format method for floats appears to always output the leading "0" even in cases where doing so cause the string to unnecessarily exceed the requested field width.  It does seem to be a bit of an edge case.  But I don't see anything in the current documentation or in PEP 3101 that addresses this case one way or another.  Eric, what's your take on this?
msg212494 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2014-03-01 10:39
I think you always want the leading zero. Mark (Dickinson), what do yo think?

And I also think changing it at this point would be problematic.
msg212495 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2014-03-01 10:43
Oops, not sure how the nosy list got changed. Sorry about that.
msg212502 - (view) Author: Mark Lawrence (BreamoreBoy) * Date: 2014-03-01 14:40
'0.12345678' or '+0.1234567' could both be considered equally valid.  Who can say which is really The One True Way? :)
msg212519 - (view) Author: (aubmoon) Date: 2014-03-01 18:49
Neither of those strictly meets the stated format. '0.12345678' is missing
the + which is explicit in the format and '+0.1234567' does not have 8
decimal places. Only '+.12345678' has a length of 10, 8 decimal places, and
the required sign. I realize this definitely an edge case, but it was
encountered in real life.

Thanks again for the careful consideration of this issue.

-Mark

On Saturday, March 1, 2014, Mark Lawrence <report@bugs.python.org> wrote:

>
> Mark Lawrence added the comment:
>
> '0.12345678' or '+0.1234567' could both be considered equally valid.  Who
> can say which is really The One True Way? :)
>
> ----------
> nosy: +BreamoreBoy
>
> _______________________________________
> Python tracker <report@bugs.python.org <javascript:;>>
> <http://bugs.python.org/issue20811>
> _______________________________________
>
msg212521 - (view) Author: Stefan Krah (skrah) * (Python committer) Date: 2014-03-01 21:57
aubmoon: Would it be a possibility just to use 'f' instead?

>>> "{:+10.7f}".format(1.12345678)
'+1.1234568'
>>> "{:+10.7f}".format(0.12345678)
'+0.1234568'
msg212526 - (view) Author: (aubmoon) Date: 2014-03-01 23:04
That is exactly what I tried first. It turns out in the particular case I
have been working the 8th digit is needed for correct answers. The job is a
port of a punch card FORTRAN system into something more modern. The catch
is the system is a scientific application that protects life, limb, and
treasure. The new system must inter-operate with the legacy system until
the entire system can be replaced with VV&A software under configuration
control. In my particular case the sign must be printed, the decimal must
be printed, and all eight digits. The number is always strictly -1 < x < 1
and has eight significant digits. The number is the included in a larger
string of data. What I have done to address the issue is format the x
string separately from the rest of the data and then slice and join
out the undesired
leading 0. Then include the value as a %s format in the larger context.

On Saturday, March 1, 2014, Stefan Krah <report@bugs.python.org> wrote:

>
> Stefan Krah added the comment:
>
> aubmoon: Would it be a possibility just to use 'f' instead?
>
> >>> "{:+10.7f}".format(1.12345678)
> '+1.1234568'
> >>> "{:+10.7f}".format(0.12345678)
> '+0.1234568'
>
> ----------
> nosy: +skrah
>
> _______________________________________
> Python tracker <report@bugs.python.org <javascript:;>>
> <http://bugs.python.org/issue20811>
> _______________________________________
>
msg212527 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2014-03-01 23:17
I still think this is a special case that we won't "fix". And even if we did, you'd have to wait until 3.5.

But instead of your solution, it might be easier to wrap your floats in a class that implements your version of format, based on float's format with some post-processing:

class MyFloat(float):
    def __format__(self, fmt):
        s = float.__format__(self, fmt)
        if s[1] == '0':
            return s[0] + s[2:]
        return s

print(format(MyFloat(0.12345678), '+8.8'))
print(format(MyFloat(1.12345678), '+8.8'))

gives:
+.12345678
+1.1234568

I've grossly simplified __format__, of course. And I'm using built-in format() instead of ''.format() (because it's less typing), but they use the same mechanisms. So:

print('{}:"{:+10.8}"'.format('answer', MyFloat(0.12345678)))

gives:
answer:"+.12345678"
msg212639 - (view) Author: (aubmoon) Date: 2014-03-03 14:40
We will add extending the float class as another possible work around or
part of a work around. Our developers now have a few options to
choose from. I am not sure which approach will be preferred. thanks for all
of the quick replies. this was just an odd case that was encountered, so I
wanted the community to be aware of it. if and when to address it is above
my level.

On Saturday, March 1, 2014, Eric V. Smith <report@bugs.python.org> wrote:

>
> Eric V. Smith added the comment:
>
> I still think this is a special case that we won't "fix". And even if we
> did, you'd have to wait until 3.5.
>
> But instead of your solution, it might be easier to wrap your floats in a
> class that implements your version of format, based on float's format with
> some post-processing:
>
> class MyFloat(float):
>     def __format__(self, fmt):
>         s = float.__format__(self, fmt)
>         if s[1] == '0':
>             return s[0] + s[2:]
>         return s
>
> print(format(MyFloat(0.12345678), '+8.8'))
> print(format(MyFloat(1.12345678), '+8.8'))
>
> gives:
> +.12345678
> +1.1234568
>
> I've grossly simplified __format__, of course. And I'm using built-in
> format() instead of ''.format() (because it's less typing), but they use
> the same mechanisms. So:
>
> print('{}:"{:+10.8}"'.format('answer', MyFloat(0.12345678)))
>
> gives:
> answer:"+.12345678"
>
> ----------
>
> _______________________________________
> Python tracker <report@bugs.python.org <javascript:;>>
> <http://bugs.python.org/issue20811>
> _______________________________________
>
msg212770 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2014-03-05 21:08
> I think you always want the leading zero. Mark (Dickinson), what do yo think?

Agreed that we want to keep the leading zero for normal uses.  I wouldn't object to some way to opt out of the leading zero, but I'm not sure what that way (w/c)ould be.
msg221084 - (view) Author: Mark Lawrence (BreamoreBoy) * Date: 2014-06-20 12:46
I cannot see a fix that would keep everybody happy.  Also allow for potential backward compatibility issues.  Given that there are at least two if not more suggested workarounds I'm inclined to close as "wont fix".  Opinions please.
History
Date User Action Args
2022-04-11 14:57:59adminsetgithub: 65010
2014-06-20 12:50:39eric.smithsetstatus: open -> closed
assignee: eric.smith
resolution: wont fix
stage: resolved
2014-06-20 12:46:21BreamoreBoysetmessages: + msg221084
2014-03-05 21:08:57mark.dickinsonsetmessages: + msg212770
2014-03-03 14:40:13aubmoonsetmessages: + msg212639
2014-03-01 23:17:00eric.smithsetmessages: + msg212527
2014-03-01 23:04:35aubmoonsetmessages: + msg212526
2014-03-01 21:57:26skrahsetnosy: + skrah
messages: + msg212521
2014-03-01 18:49:12aubmoonsetmessages: + msg212519
2014-03-01 14:40:08BreamoreBoysetnosy: + BreamoreBoy
messages: + msg212502
2014-03-01 10:43:08eric.smithsetnosy: - lemburg, gvanrossum, loewis, barry, anthonybaxter, georg.brandl, benjamin.peterson, tarek, eric.araujo
messages: + msg212495
2014-03-01 10:39:18eric.smithsetnosy: + lemburg, gvanrossum, loewis, barry, anthonybaxter, georg.brandl, benjamin.peterson, tarek, eric.araujo
messages: + msg212494
2014-03-01 09:36:16mark.dickinsonsetnosy: + mark.dickinson
2014-03-01 02:48:36ned.deilysetstatus: closed -> open

nosy: + eric.smith
messages: + msg212484

resolution: not a bug -> (no value)
stage: resolved -> (no value)
2014-03-01 00:48:53aubmoonsetmessages: + msg212483
2014-02-28 22:32:04ned.deilysetstatus: open -> closed

nosy: + ned.deily
messages: + msg212480

resolution: not a bug
stage: resolved
2014-02-28 21:53:49aubmooncreate