Signed zeros: is this a bug?
Alex Martelli
aleax at mac.com
Sun Mar 11 13:26:57 EDT 2007
Mark Dickinson <dickinsm at gmail.com> wrote:
> I get the following behaviour on Python 2.5 (OS X 10.4.8 on PowerPC,
> in case it's relevant.)
>
> >>> x, y = 0.0, -0.0
> >>> x, y
> (0.0, 0.0)
> >>> x, y = -0.0, 0.0
> >>> x, y
> (-0.0, -0.0)
>
> I would have expected y to be -0.0 in the first case, and 0.0 in the
> second. Should the above be considered a bug, or is Python not
> expected to honour signs of zeros? I'm working in a situation
> involving complex arithmetic where branch cuts, and hence signed
> zeros, are important, and it would be handy if the above code could be
> relied upon to do the right thing.
Looks to me like you've found a bug with the parser/compiler:
>>> c=compile('-0.0,0.0', 'eval', 'eval')
>>> eval(c)
(-0.0, -0.0)
>>> dis.dis(c)
1 0 LOAD_CONST 1 ((-0.0, -0.0))
3 RETURN_VALUE
Similar problems appear in parsing display forms of lists and dicts and
calls to functions with constant arguments (among which a constant 0.0
and a constant -0.0) -- and more generally when both constants -0.0 and
0.0 appear within the same "unit of compilation" (expression, function,
...) as for example in:
>>> def f():
... a = -0.0
... return a, 0.0
...
>>> f()
(-0.0, -0.0)
>>>
Why I think it has specifically to do with parsing/compiling, e.g.:
>>> {23:-0.0, 45:0.0}
{45: -0.0, 23: -0.0}
>>> x=0.0
>>> y=-0.0
>>> {23:x, 45:y}
{45: -0.0, 23: 0.0}
>>>
so there appears to be no problem once the 0.0 and -0.0 values come from
variables or the like -- only if they're both from constants within the
same "unit of compilation". Indeed, if you put the code above in a
function, rather than in separate entries to the toplevel
interpreter...:
>>> def g():
... x = 0.0
... y = -0.0
... return {23:x, 45:y}
...
>>> g()
{45: 0.0, 23: 0.0}
>>>
...the bug surfaces again. Looks a bit like the result of a slightly
errant "peephole optimizer" pass which tries to roll multiple
occurrences of "the same constant" within the same codeobject into a
single one, and consider two immutables a "multiple occurrence" if
they're == to each other (as, indeed, 0.0 and -0.0 must be).
The Python-coded compiler package does NOT give the problem:
>>> compiler.compile('-0.0,0.0','zap','eval')
<code object <expression> at 0x70068, file "zap", line 1>
>>> dis.dis(_)
1 0 LOAD_CONST 1 (0.0)
3 UNARY_NEGATIVE
4 LOAD_CONST 1 (0.0)
7 BUILD_TUPLE 2
10 RETURN_VALUE
...presumably because it doesn't do peephole optimization (nor any other
kind of optimization).
Unfortunately I've never really looked in depth into these parts of
Python so I can't help much; however, if you open a bug at
<http://sourceforge.net/tracker/?group_id=5470> I think you stand a good
chance of eventually seeing it fixed in 2.5.x for some x.
Python/peephole.c does appear to be taking some care in constant
folding:
192 case UNARY_NEGATIVE:
193 /* Preserve the sign of -0.0 */
194 if (PyObject_IsTrue(v) == 1)
195 newconst = PyNumber_Negative(v);
so I suspect the problem is elsewhere, but I can't find it yet.
Alex
In the meantime, I hope that some available workarounds for the bug are
clear from this discussion: avoid using multiple constants in a single
compilation unit where one is 0.0 and another is -0.0, or, if you really
can't avoid that, perhaps use compiler.compile to explicitly build the
bytecode you need.
More information about the Python-list
mailing list