[Python-Dev] Floor division

Tim Peters tim.peters at gmail.com
Tue Jan 23 11:58:52 CET 2007


[Guido]
>>> I guess the conjugate() function could also just return self (although I see
>>> that conjugate() for a complex with a zero imaginary part returns
>>> something whose imaginary part is -0; is that intentional?

[TIm Peters]
>> That's wrong, if true:  it should return something with the opposite
>> sign on the imaginary part, whether or not that equals 0 (+0. and -0.
>> both "equal 0").

|[Nick Maclaren]
> Grrk.  Why?  Seriously.

Seriously:  because there's some reason to do so and no good reason
not to.  This is the current complex conjugate implementation:

static PyObject *
complex_conjugate(PyObject *self)
{
	Py_complex c;
	c = ((PyComplexObject *)self)->cval;
	c.imag = -c.imag;
	return PyComplex_FromCComplex(c);
}

Complicating that to make a special case of c.imag == 0 is simply
goofy without a /compelling/ reason to do so.  As I read the C99
section on "conj", it's also their intent that the sign on the
imaginary part be flipped regardless of value.

> IEEE 754 signed zeroes are deceptive enough for float, but are
> a gibbering nightmare for complex; Kahan may be
> able to handle them, but mere mortals can't.  Inter alia, the only
> sane forms of infinity for complex numbers are a SINGLE one (the
> compactified model) and to may infinity into NaN (which I prefer,
> as it leads to less nonsense).
>
> And, returning to 'floor' - if one is truncating towards -infinity,
> should floor(-0.0) deliver -1.0, 0.0 or -0.0?

I'd leave a zero argument alone (for ceiling too), and am quite sure
that's "the right" 754-ish behavior.

>> math.fmod is 15 years old -- whether or not someone likes it has
>> nothing to do with whether Python should stop trying to use the
>> current integer-derived meaning of % for floats.

> Eh?  No, it isn't.  Because of the indirection to the C library, it
> is changing specification as we speak!  THAT is all I am getting at;
> not that the answer might not be A math.fmod with defined behaviour.

Couldn't quite parse that, but nearly all of Python's math-module
functions inherit most behavior from the platform libm.  This is often
considered to be a feature:  the functions called from Python
generally act much like they do when called from C or Fortran on the
same platform, easing cross-language development on a single platform.

It's never been Python's intent to define all behavior here, and
largely because Python isn't a math-library development project.  To
the extent that enough people care enough to standardize C's libm
endcase behavior across platforms, Python inherits that too.  Not much
of an inheritance so far ;-)

Do note the flip side:  to the extent that different platform
religions refuse to standardize libm endcase behavior, Python plays
along with whatever libm gods the platform it's running on worships.
That's of value to some too.

>> On occasion we've added additional error checking around functions
>> inherited from C.  But adding code to return a NaN has never been
>> done.  If you want special error checking added to the math.fmod
>> wrapper, it would be easiest to "sell" by far to request that it raise
>> ZeroDivisionError (as integer mod does) for a modulus of 0, or
>> ValueError (Python's historic mapping of libm EDOM, and what Python's
>> fmod(1, 0) already does on some platforms).  The `decimal` module
>> raises InvalidOperation in this case, but that exception is specific
>> to the `decimal` module for now.

> I never said that it should; I said that it is reasonable behaviour on systems
> that support them.  I personally much prefer an exception in this case.

So which one would you prefer?  As explained, there are 3 plausible candidates.

You seem to be having some trouble taking "yes" for an answer here ;-)

> What I was trying to point out is that the current behaviour is
> UNDEFINED (and may give total nonsense).  That is not
> good.

Eh -- I can't get excited about it.  AFAIK, in 15 years nobody has
complained about passing a 0 modulus to math.fmod (possibly because
most newbies use the Windows distro, and it does raise ValueError
there).

>>>> For ints and floats, real could just return self, and imag could
>>>> return a 0 of the same type as self. I guess the conjugate() function
>>>> could also just return self (although I see that conjugate() for a
>>>> complex with a zero imaginary part returns something whose imaginary
>>>> part is -0; is that intentional? I'd rather not have to do that when
>>>> the input is an int or float, what do you think?)

>>> I don't see the problem in doing that - WHEN implicit conversion
>>> to a smaller domain, losing information, raises an exception.

>> Take it as a pragmatic fact that it wouldn't.  Besides, e.g., the
>> conjugate of 10**50000 is exactly 10**50000 mathematically.  Why raise
>> an exception just because it can't be represented as a float?  The
>> exact result is easily supplied with a few lines of "obviously
>> correct" implementation code (incref `self` and return it).

> Eh?  I don't understand.  Are you referring to float("1.0e50000"),
> pow(10,50000), pow(10.0,50000), or a conjugate (and, if so, of what?)

"The conjugate of 10**50000", which is an example of something Guido
was asking about ("I'd rather not have to do that when the input is an
int ...").  In a Python extended in the way he's imagining, one
concrete way to spell it would be:

    (10**50000).conjugate()

Complex numbers already have a conjugate() method:

>>> 1j.conjugate()
-1j

Guido is asking about adding that method to floats/ints/longs too.  If
he did, what should it do?  The only way I could read your original
response is that you wanted, e.g., (1.23).conjugate() to return a
complex number with a -0.0 imaginary part, likewise for
(123).conjugate(), but that you wanted some_large_int.conjugate() to
raise an exception since the implicit coercion to float would lose
information.

What Guido would rather do, which I agreed with, was to have
x.conjugate() simply return x when x is float/int/long.  No change in
value, no change in type, and the obvious implementation would even
make

    x.conjugate() is x

true for x an int/long/float.

> float(conjg(1.23)) obviously need not raise an exception, except
> possibly "Sanity failure" :-)

Well, Python /does/ raise an exception (TypeError) if you try to apply
float() to a complex argument.  That's solely type-based -- the value
of the imaginary part is irrelevant.

    >>> float(complex(0))
    Traceback (most recent call last):
       ...
    TypeError: can't convert complex to float; use abs(z)

So if (1.23).conjugate() returned a complex number, applying float()
to that would raise an exception.  If it returned 1.23, then of course
applying float() to that would not be exceptional.


More information about the Python-Dev mailing list