classification
Title: str.format() wrongly formats complex() numbers (Py30a2)
Type: enhancement Stage: patch review
Components: Interpreter Core Versions: Python 3.1, Python 2.7
process
Status: closed Resolution: accepted
Dependencies: Superseder:
Assigned To: eric.smith Nosy List: ajaksu2, eric.smith, gvanrossum, mark, mark.dickinson
Priority: normal Keywords: patch

Created on 2007-12-11 13:30 by mark, last changed 2009-05-04 11:39 by mark.dickinson. This issue is now closed.

Files
File name Uploaded Description Edit
issue-1588-trunk.patch eric.smith, 2009-04-28 22:47
issue-1588-py3k.patch eric.smith, 2009-04-28 22:48
Messages (39)
msg58428 - (view) Author: Mark Summerfield (mark) Date: 2007-12-11 13:30
>>> x = complex(1, 2/3)
>>> "{0} {0:.5}".format(x)
'(1+0.666666666667j) (1+0.'

The complex number is being formatted as if it were a string and simply
truncated to 5 characters. I would expect each part of the complex
number to be formatted according to the format specifier, i.e., in the
case of :.5 to both have 5 digits after the decimal point.
msg58447 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2007-12-11 17:56
This really is a feature request -- in Python 2.x there is no formatting
code for complex numbers at all, and "%.5s" % complex(...) does the same
thing.

I agree it would be neat to have control over complex numbers using the
same formatting language used for floats; but I note that it's easy
enough to do this manually, e.g.

>>> "{0.real:.5}+{0.imag:.5}j".format(z)
'1+0.66667j'
msg58448 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2007-12-11 17:57
Maybe this would be a good GHOP task?
msg58483 - (view) Author: Mark Summerfield (mark) Date: 2007-12-12 07:42
On 2007-12-11, Guido van Rossum wrote:
> Guido van Rossum added the comment:
>
> This really is a feature request -- in Python 2.x there is no formatting
> code for complex numbers at all, and "%.5s" % complex(...) does the same
> thing.

I thought Python 3 was meant to be an _improvement_:-)

> I agree it would be neat to have control over complex numbers using the
> same formatting language used for floats; but I note that it's easy
> enough to do this manually, e.g.
>
> >>> "{0.real:.5}+{0.imag:.5}j".format(z)
>
> '1+0.66667j'

Good point, I'll use that.

Thanks!
msg58484 - (view) Author: Mark Summerfield (mark) Date: 2007-12-12 08:22
On 2007-12-11, Guido van Rossum wrote:
> Guido van Rossum added the comment:
>
> This really is a feature request -- in Python 2.x there is no formatting
> code for complex numbers at all, and "%.5s" % complex(...) does the same
> thing.
>
> I agree it would be neat to have control over complex numbers using the
> same formatting language used for floats; but I note that it's easy
> enough to do this manually, e.g.
>
> >>> "{0.real:.5}+{0.imag:.5}j".format(z)
>
> '1+0.66667j'

That's not quite right because it doesn't always handle the sign
correctly and doesn't force float output. So I think it should be this:

'1.00000+0.66667j'
>>> "{0.real:.5f}{0.imag:+.5f}j".format(complex(1, -2/3))
'1.00000-0.66667j'
msg58496 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2007-12-12 15:11
> I thought Python 3 was meant to be an _improvement_:-)

That's why I didn't close the issue but reclassified it.

Or did you expect me to implement it overnight? :-)
msg86640 - (view) Author: Daniel Diniz (ajaksu2) Date: 2009-04-27 01:29
Confirmed in py3k at rev71995.
msg86651 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2009-04-27 09:10
I agree this is a feature request. It comes down to:

What should the format specifier mini-language for complex numbers look
like?

Should it look like the existing mini-language for floats, but have the
format specified twice, with some sort of delimiter? Or just specified
once, and use that for both parts?

I'm sure python-ideas could argue over it for ages, but I don't see any
outcome that's much of an improvement over the suggested:
"{0.real:.5f}{0.imag:+.5f}j".format(complex(1, -2/3))
msg86652 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2009-04-27 09:26
> What should the format specifier mini-language for complex numbers look
> like?
> Should it look like the existing mini-language for floats, but have
> the format specified twice, with some sort of delimiter?

This sounds clumsy to me.  I'd guess that in most uses you'd want the
same format for both pieces.

> Or just specified once, and use that for both parts?

That doesn't sound unreasonable.  But there might need to be some
thinking about exactly what a '+' modifier means, or how you pad with
zeros on the left when you've got two pieces to pad.

It seems simplest just to tell people to format the real and imaginary
parts by hand.  As it isn't totally obvious how to do this (e.g.,
remembering the '+' for the imaginary part), perhaps there should be a
recipe in the docs somewhere?
msg86656 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2009-04-27 11:32
Mark Dickinson wrote:
>> What should the format specifier mini-language for complex numbers look
>> like?
>> Should it look like the existing mini-language for floats, but have
>> the format specified twice, with some sort of delimiter?
> 
> This sounds clumsy to me.  I'd guess that in most uses you'd want the
> same format for both pieces.

I agree, and mostly I was just trying to spark some discussion and show 
how (absurdly) far we can take this.

>> Or just specified once, and use that for both parts?
> 
> That doesn't sound unreasonable.  But there might need to be some
> thinking about exactly what a '+' modifier means, or how you pad with
> zeros on the left when you've got two pieces to pad.

How about this:
- we have a single specifier with the same format as floats
- we force the sign on the imaginary part to be '+', no
   matter what was specified
- we add a 'j' after the imaginary part
- we ignore any width specified (and therefor any alignment
   and padding)

> It seems simplest just to tell people to format the real and imaginary
> parts by hand.  As it isn't totally obvious how to do this (e.g.,
> remembering the '+' for the imaginary part), perhaps there should be a
> recipe in the docs somewhere?

When we document the above approach, we note the way to get full control 
as mentioned in a prior message.

I guess we should put the docs in with string formatting (since that's 
where the other builtin types are documented), although really it 
belongs in complex.__format__ by itself. But I doubt anyone would find 
it there. Maybe we could to add a pointer from the string formatting to 
complex.__format__.
msg86679 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2009-04-27 16:51
> How about this:

> - we have a single specifier with the same format as floats
> - we force the sign on the imaginary part to be '+', no
>    matter what was specified
> - we add a 'j' after the imaginary part

This sounds good to me.  I assume a '+' would still affect
the sign of the real part?

> - we ignore any width specified (and therefor any alignment
>    and padding)

I don't see any problem with dealing with width, alignment
and padding with a user-specified fill character;  I think we
should keep these if possible.  It's just zero padding where
it's not clear what should happen.

For the bits that are disabled (e.g., zero padding), should
there be a ValueError raised, or do those bits just get
silently ignored?
msg86680 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2009-04-27 16:58
> I don't see any problem with dealing with width, alignment
> and padding with a user-specified fill character;  I think we
> should keep these if possible.  It's just zero padding where
> it's not clear what should happen.

You're correct. It's just zero padding that would be disabled.

> For the bits that are disabled (e.g., zero padding), should
> there be a ValueError raised, or do those bits just get
> silently ignored?

I think a ValueError would be best. That way if we decide to give it some
meaning in the future, we know it won't change any working code.
msg86681 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2009-04-27 17:01
More specifically, how about allowing widths, and the
'<', '>' and '^' alignment specifiers, but not '=', or
'0' for zero-padding.

I suppose that thousands separators should be permitted
here too?  Though it's difficult to imagine anyone actually
using them.  If we allow ',' but not '0' then we avoid
the crazy zero-padding--thousands-separators interaction.
msg86682 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2009-04-27 17:03
> This sounds good to me.  I assume a '+' would still affect
> the sign of the real part?

Forgot to reply to this part.

Yes, a '+', '-', or ' ' would still affect the real part, but the
imaginary part would always use '+'.
msg86683 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2009-04-27 17:04
> I think a ValueError would be best. That way if we decide to give it
> some meaning in the future, we know it won't change any working code.

Agreed.  It also fits with the way that other non-numeric types seem to 
behave, as in:

>>> format("boris", "030s")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: '=' alignment not allowed in string format specifier
msg86686 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2009-04-27 17:06
> More specifically, how about allowing widths, and the
> '<', '>' and '^' alignment specifiers, but not '=', or
> '0' for zero-padding.

That sounds correct.

> I suppose that thousands separators should be permitted
> here too?  Though it's difficult to imagine anyone actually
> using them.  If we allow ',' but not '0' then we avoid
> the crazy zero-padding--thousands-separators interaction.

That was my thinking, too.
msg86717 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2009-04-28 09:23
I'm also going to disallow the '%' format code. I don't think it makes
any sense to convert a complex number to a percentage.
msg86718 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2009-04-28 09:32
> I'm also going to disallow the '%' format code.

Sounds good to me.

> I don't think it makes any sense to convert a complex number to a
> percentage.

Well, I think it's clear what the numbers would be (just scale both real
and imaginary parts by 100 before using fixed-point formatting).  The
real issue whether to have two trailing '%'s or one.

Just being difficult:  I completely agree that '%' should be disallowed
for complex numbers.
msg86719 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2009-04-28 09:39
Two things that haven't come up so far:

(1) What about parentheses? The current complex repr and str have
parentheses in them, for reasons that I still don't really understand.

I'd suggest leaving them out altogether;  except that I have
the impression (perhaps wrongly) that an empty type code is
supposed to correspond to str.  And given that I don't understand
why the parens were in there in the first place, I'm probably
not a good person to judge whether they should stay in a
formatted complex number.

(2) What about zeros?  The current repr and str leave out the real
part (and the enclosing parens) if it's equal to zero.  Should
format do the same?  I'd say not, except possibly again in the
case where there's no type code.
msg86720 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2009-04-28 10:01
Mark Dickinson wrote:
> (1) What about parentheses? The current complex repr and str have
> parentheses in them, for reasons that I still don't really understand.
> 
> I'd suggest leaving them out altogether;  except that I have
> the impression (perhaps wrongly) that an empty type code is
> supposed to correspond to str.  And given that I don't understand
> why the parens were in there in the first place, I'm probably
> not a good person to judge whether they should stay in a
> formatted complex number.

The rule is that if that x.__format__('') is equivalent to str(x). All 
of the built-in objects have a test for a zero-length format string and 
delegate to str(). But (3).__format__('-') does not call str(), despite 
the fact that it's the identical output. That's because the format 
string isn't zero-length. Instead, this is the case of the missing 
format "presentation type".

I couldn't find a case with any built-in objects where this really makes 
a difference (although I can't say I spent a lot of time at it). Complex 
would be the first one. But that doesn't really bother me.

format(1+1j, '')  -> '(1+1j)'
format(1+1j, '-') -> '1+1j'

Although I guess if we wanted to, we could say that the empty 
presentation type is equivalent to 'g', but gives you parens. This would 
fit in nicely with issue 5858, if it's accepted. Floats do something 
similar and special case the empty presentation type: '' is like 'g', 
but with at least one digit after the decimal point.

> (2) What about zeros?  The current repr and str leave out the real
> part (and the enclosing parens) if it's equal to zero.  Should
> format do the same?  I'd say not, except possibly again in the
> case where there's no type code.

I agree. Again, we could say that the empty presentation type is 
different in this regard.
msg86721 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2009-04-28 10:26
> Complex would be the first one. But that doesn't really bother me.

It bothers me a little.  I see '' as a special case of the empty
presentation type, even if that's not what a strict reading of
PEP 3101 says, so I expect '', '>' '<20' all to format the
number in the same way, and only differ in their treatment of
alignment and padding.  That is, adding a '>' to the start of a
format specifier shouldn't change the formatting of the number
itself.  So from this perspective, it seems better if format(x, '')
ends up doing the same thing as str(x) as a result of the
choices made for the empty presentation type, rather than
as a result of special-casing ''.

> Although I guess if we wanted to, we could say that the empty 
> presentation type is equivalent to 'g', but gives you parens.

This works for me.

[about suppressing real zeros...]
> Again, we could say that the empty presentation type is 
> different in this regard.

Makes sense.  Does treating the empty presentation type as special this
way add much extra complication to the implementation?
msg86722 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2009-04-28 10:34
Mark Dickinson wrote:
>> Although I guess if we wanted to, we could say that the empty 
>> presentation type is equivalent to 'g', but gives you parens.
> 
> This works for me.

Me, too.

> [about suppressing real zeros...]
>> Again, we could say that the empty presentation type is 
>> different in this regard.
> 
> Makes sense.  Does treating the empty presentation type as special this
> way add much extra complication to the implementation?

No. I'm basically finished with it. Before I check it in, I'll attach a 
patch (against trunk) so you can look at how it works.
msg86724 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2009-04-28 10:56
See the attached patch. Comments welcome.

I'm not sure I'm doing the right thing with 'g' and appending zeros:
>>> format(3+4j, '.2')
'(3+4j)'
>>> format(3+4j, '.2g')
'3.0+4.0j'
>>> format(3+0j, '.2')
'(3+0j)'
>>> format(3+0j, '.2g')
'3.0+0.0j'
>>> format(1j, '.2')
'(1j)'
>>> format(1j, '.2g')
'0.0+1.0j'
msg86725 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2009-04-28 11:06
I'll take a look.

The trailing zeros thing is heavily bound up with issue 5858, of course;
I think we need a decision on that, one way or the other.  One problem
is that I'm only proposing the issue 5858 change for py3k, not trunk.
msg86726 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2009-04-28 11:15
Mark Dickinson wrote:
> The trailing zeros thing is heavily bound up with issue 5858, of course;
> I think we need a decision on that, one way or the other.  One problem
> is that I'm only proposing the issue 5858 change for py3k, not trunk.

I don't have a problem with trunk's complex.__format__ not agreeing with 
trunk's complex.__str__. If it's a big deal, we can just take 
complex.__format__ completely out of trunk with a #define. In any event, 
there's lots of time before 2.7 and not so much before 3.1, so let's 
concentrate on trunk. Which is what I should have done with starting 
this issue (but forward porting is easier for me that back porting).
msg86727 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2009-04-28 11:36
Here's a patch against py3k, with one slight change with non-empty
presentation types.
msg86731 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2009-04-28 12:49
With your patch, I'm getting quite strange results when using alignment
specifiers:

>>> z = 123+4j
>>> format(z, '=20')
'(                 123+                  4j)'
>>> format(z, '^20')
'(        123                  +4         j)'
>>> format(z, '<20')
'(123                 +4                  j)'
>>> len(format(z, '<20'))
43

Is this intentional?  I was expecting to get strings of length 20,
with the substring '(123+4j)' positioned either in the middle
or on the left or right.
msg86732 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2009-04-28 12:51
...

> Is this intentional?  I was expecting to get strings of length 20,
> with the substring '(123+4j)' positioned either in the middle
> or on the left or right.

No, not intentional. I'll fix it and add some tests. Thanks.
msg86755 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2009-04-28 17:33
This is a patch against py3k, including tests in test_complex.py. It
should deal with the padding, but let me know.
msg86766 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2009-04-28 20:37
I also propose to disallow the '=' alignment flag. It means put the
padding between the sign and the digits, and since there are 2 signs,
it's not clear what this would mean.

Remember, by using .real and .imag, you can achieve this level of
control in the formatting, anyway.
msg86772 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2009-04-28 22:47
I think these patches are complete. One for py3k, one for trunk. If no
complaints, I'll apply them before this weekend's py3k beta.
msg86827 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2009-04-29 22:05
With these patches, all tests pass for me both for py3k and trunk.
msg86829 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2009-04-29 22:11
I haven't done as thorough a review as I'd like, but both
patches look good to me.  I recommend applying them.
msg86836 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2009-04-30 01:01
Thanks, Mark.

I'm not so worried about the code, but more so the tests. As far as the
code goes, it's really a combination of float and string formatting. I
copied the float formatting and refactored the string formatting so I
could reuse it.

But of course, another set of eyes to review it is always welcome. If
you find anything, I'll fix it. I'm closing this issue.

Committed in trunk in r72137, and in py3k in r72140.
msg86964 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2009-05-02 18:34
One comment on the new complex formatting.  I now get (in py3k)

>>> from math import pi, e
>>> format(complex(pi,e), '<')
'(3.14159+2.71828j)'
>>> format(complex(pi,e), '')
'(3.14159265359+2.71828182846j)'

I understand why this is happening, but again I think that alignment 
flags shouldn't change the form of the number itself.  Would it be 
reasonable to have the empty format code always use a precision of 12?
msg86972 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2009-05-02 19:33
Is this suggestion for all types, or just complex? Because float has the
same issue.

>>> format(pi, '')
'3.14159265359'
[38243 refs]
>>> format(pi, '>')
'3.14159'
msg86973 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2009-05-02 19:37
Hmm.  That also seems wrong to me.  So I guess it's a suggestion
for float as well, which means it's not specific to this issue.
Should I open a separate feature request?
msg86976 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2009-05-02 19:57
> Hmm.  That also seems wrong to me.  So I guess it's a suggestion
> for float as well, which means it's not specific to this issue.
> Should I open a separate feature request?

Yes, this is a separate issue. It comes from PEP 3101's specification of 
"like 'g' but different" for floats with no specified presentation type.
msg87116 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2009-05-04 11:39
> Yes, this is a separate issue.

Thanks.  See issue 5920.
History
Date User Action Args
2009-05-04 11:39:04mark.dickinsonsetmessages: + msg87116
2009-05-02 19:57:52eric.smithsetmessages: + msg86976
2009-05-02 19:37:45mark.dickinsonsetmessages: + msg86973
2009-05-02 19:33:22eric.smithsetmessages: + msg86972
2009-05-02 18:34:48mark.dickinsonsetmessages: + msg86964
2009-04-30 01:01:40eric.smithsetstatus: open -> closed
resolution: accepted
messages: + msg86836
2009-04-29 22:11:37mark.dickinsonsetmessages: + msg86829
2009-04-29 22:05:35mark.dickinsonsetmessages: + msg86827
2009-04-28 22:48:36eric.smithsetfiles: - issue-1588-2-py3k.patch
2009-04-28 22:48:30eric.smithsetfiles: - issue-1588-1-py3k.patch
2009-04-28 22:48:22eric.smithsetfiles: - issue-1588-0.patch
2009-04-28 22:48:16eric.smithsetfiles: + issue-1588-py3k.patch
2009-04-28 22:47:38eric.smithsetfiles: + issue-1588-trunk.patch

messages: + msg86772
2009-04-28 20:37:58eric.smithsetpriority: low -> normal

messages: + msg86766
2009-04-28 17:33:48eric.smithsetfiles: + issue-1588-2-py3k.patch

messages: + msg86755
stage: test needed -> patch review
2009-04-28 12:51:47eric.smithsetmessages: + msg86732
2009-04-28 12:49:04mark.dickinsonsetmessages: + msg86731
2009-04-28 11:36:41eric.smithsetfiles: + issue-1588-1-py3k.patch

messages: + msg86727
2009-04-28 11:15:43eric.smithsetmessages: + msg86726
2009-04-28 11:06:39mark.dickinsonsetmessages: + msg86725
stage: patch review -> test needed
2009-04-28 10:56:19eric.smithsetkeywords: + patch
files: + issue-1588-0.patch
messages: + msg86724

stage: test needed -> patch review
2009-04-28 10:34:09eric.smithsetmessages: + msg86722
2009-04-28 10:26:40mark.dickinsonsetmessages: + msg86721
2009-04-28 10:01:58eric.smithsetmessages: + msg86720
2009-04-28 09:39:38mark.dickinsonsetmessages: + msg86719
2009-04-28 09:32:20mark.dickinsonsetmessages: + msg86718
2009-04-28 09:23:40eric.smithsetmessages: + msg86717
2009-04-27 17:06:59eric.smithsetmessages: + msg86686
2009-04-27 17:04:14mark.dickinsonsetmessages: + msg86683
2009-04-27 17:03:18eric.smithsetmessages: + msg86682
2009-04-27 17:01:43mark.dickinsonsetmessages: + msg86681
2009-04-27 16:58:29eric.smithsetmessages: + msg86680
2009-04-27 16:51:51mark.dickinsonsetmessages: + msg86679
2009-04-27 12:06:42eric.smithsetassignee: eric.smith
2009-04-27 11:32:08eric.smithsetmessages: + msg86656
2009-04-27 09:26:51mark.dickinsonsetmessages: + msg86652
2009-04-27 09:10:38eric.smithsettype: behavior -> enhancement
messages: + msg86651
versions: + Python 2.7, - Python 3.0
2009-04-27 01:29:56ajaksu2setversions: + Python 3.1
nosy: + ajaksu2, mark.dickinson

messages: + msg86640

stage: test needed
2008-05-06 13:54:07eric.smithsetnosy: + eric.smith
2007-12-12 15:11:06gvanrossumsetmessages: + msg58496
2007-12-12 08:22:15marksetmessages: + msg58484
2007-12-12 07:42:45marksetmessages: + msg58483
2007-12-11 17:57:09gvanrossumsetmessages: + msg58448
2007-12-11 17:56:41gvanrossumsetpriority: low
nosy: + gvanrossum
messages: + msg58447
2007-12-11 13:30:51markcreate