Make float.__(i/r)floordiv__ return an int

It has everything to do with this discussion. You are saying that floordiv should return an integer to match math.floor, and I was saying that floordiv is the correct one between the two.

Is this possible if subnormals are disabled? Or does that not count as an IEEE-754 system? (Given that we accept other anomalies relating to signalling and quiet NaN, it might count.)

Though I’m not sure of any way to disable subnormals for testing, so that may be moot anyway.

Agreed. Indeed, under the rules of PEP 387 I believe this would be a necessary condition for making the proposed change. (But by no means sufficient. Finding at least one core developer who supported the change would also be helpful, for example.)

Also agreed. That is, I can see possible mechanisms for this, but nothing whose complexity doesn’t vastly outweigh the potential benefits.

I believe that leaves you at step 6 of the “Making Incompatible Changes” section of PEP 387:

  1. If a warning cannot be provided to users, consult with the steering council.

Note: “should we have done this differently in the first place?” is a very different question from “should we change the established behaviour now?”. I’m not 100% sure what the answer to the first question is, but it’s academic if the answer to the second is a loud clear “No!”.

3 Likes

Yep. See python - Possible to have ``a < b and not(a - b < 0)`` with floats - Stack Overflow for example, horribly hacky, platform-specific code.

FWIW, it seems to me that @ntessore is actually making a similar point to you in advancing that math.floor, .ceil, etc. should in fact return floats rather than ints for float inputs, and by extension that floor division is indeed correct in returning a float (as you also support).

This would almost certainly need a PEP, and thus convincing at least one core dev or PEP editor would also be a necessary (but also far from sufficient) requirement to move this forward, as such would be needed to sponsor said PEP. FWIW, as one, I don’t have strong feelings on this but am not certainly convinced the breakage, which seems quite difficult to warn about, would be worth the claimed benefits (assuming for the benefit of the doubt that they are indeed present).

I’ll note that in PEP-3141, the docstring of the provided code does imply that __floordiv__ return integers the same as __floor__ and __ceil__ and __round__-with-a-single-argument. See the Real class, which is the ABC equivalent of float, all four methods use the same “Integral” wording.
The actual recommendation section doesn’t mention // and __floordiv__, so it’s understandable that it was not implemented at the time (and it also recommended floor, ceil and round to keep returning floats but only for 2.6, otherwise the word used was Integral, the same as the __floordiv__ docstring). This looks more and more like an oversight.
My proposition would then be a sort of continuation of 3141, finishing it and making it consistent (I don’t know if it changes anythong about requiring another PEP), while their (sorry, @steven.daprano and @ntessore) proposition is a revert of 3141’s entire stance on floor, floordiv, round and ceil. Which they’re certainly allowed to introduce, but it’s undoubtedly more consequential, and in reverse of the direction Python has taken since 15 years ago (if I believe the date on 3141).

Division of float currently throws ZeroDivisionError for division by zero.
In numpy division of numpy.float64 gives a runtime warning for division by zero.

It looks like the philosophy in Python is to be vocal about the division by zero. Seems consistent for // to be vocal about division by zero too.

This is not an impediment to obtaining float('inf') or float('nan') as result, since one can either use numpy.float64 or catch ZeroDivisionError and return the corresponding value.

Okay, I’m gonna dismiss that as “you can break ANYTHING if you use ctypes” :slight_smile: Good to know.

2 Likes

I just did the same search (should have read the rest of this thread first!) If nothing else, a doc PR would be good. Maybe to:

In fact, it would be good to document what all the binary arithmetic operations do with regard to type. And putting it in that context does make that point that I think they all do the same thing with mixed types. So while inconsistent with floor, the current behaviour is consistent with the rest of the arithmetic operators.

As an academic interest I think it’s worth talking about :slight_smile: – but another academic question is whether it was done this way deliberately or as an oversight. As noted above – It very well could have been a deliberate decision to keep // consistent with the other arithmetic operators – or deliberate to keep it consistent with what the py2 __future__ import did.

Thanks @mdickinson for that excellent code!

What’s the use case is for float.hex? If it’s useful, why isn’t there an int.hex?

Getting the raw IEE 754 binary value of a float as a str, which could conceivably be useful for introspection, raw manipulation and interoperability, serialization, communication, etc.

Well, there’s the hex() builtin, but the semantics are different—it is simply the integer number represented in hex, as opposed to the hex of the bits in the underlying implementation. And fundamentally, the hex value of the underlying bytes of an integer are of course not compatible with those of a float in either direction—interpreting one as the other would result in, to borrow a word from another commentor, “nonsense”. This is too low a level of implementation details for the abstraction of int being a subtype of float to be type safe.

In numpy division of numpy.float64 gives a runtime warning for division by zero.
[/quote]

Note that the primary reason that numpy doesn’t raise (by default) is that it’s “vectorized” – if you have one zero in a million element array, you really don’t want the computation to stop dead. Rather, you can check for finite values at the end of the entire computation.

I think the issue at hand is that: the IEEE special values propogate through // :

In [21]: math.nan // 2
Out[21]: nan

but they would have to raise if //`` returned an int, as there are no int equivalents for Inf, NaN`, and friends.

Which brings up a thought – now that ints are “long ints”, they are not directly tied to the underlying long int C data type – so maybe it could have inf and NaN values … This would be distinct from all other (most other?) languages, so may not be worth it, but it would close one gap in the numeric tower.

A lot of code, including stdlib code, is relying on int only having actual-number values. Doing this change would raise a lot of new questions : what is range supposed to do when passed a Nan ? And slicing ? It also makes a lot of operations “unsafe” - meaning they can return a nan when they previously couldn’t - and the code using it as well.
Personnaly, I wouldn’t be sold about adding inf, nan to float itself if it were on the table - which it is not - so I’m just as unconvinced about adding those to int.

Except if it can make my proposition accepted and add my name to Python’s credits of course :upside_down_face:

More seriously though, what about the solution where // returns either an int (or a ZeroDivisionError), or a float but just in the case of inf and nan ?
It would make the operator returning different types besed upon the value of its parameters, which sounds weird even to me (I’m not convinced by this very idea, I just think it’s worth mentioning), but if those three values (-inf, nan and +inf) are special enough to be the only ones currently returned by // on which .is_integer() is not True, and if they are special enough to warrant an exception in math.ceil, math.floor and round (and in // in my opinion), maybe they are special enough to break the single-return-type rule :person_shrugging:

Backwards compatibility is always important, but I’m not sure this would be so bad – passing a non-finite value would raise an exception just like passing a None would now. But now that I think about it more, one could certainly argue that there is no need for special values for int, as you can always use sentinels that are another type.

Python didn’t originally have proper support for the IEEE special values, perhaps for this very reason. It was added on later (a bit piecemeal) I think because interaction with non-python libs (e.g. numpy) makes it unavoidable.

So perhaps operations on floats could return special values rather than raise:

math.nan // 5.0 is None
But that would be a divergence from Python’s current behavior, e.g. ZeroDivision Error. It might make some sense though now that we have comprehension and a LOT of iteration-based programming.

In [24]: [5.0 / x for x in (1,2,3,4,5,6,0,5,2)]
---------------------------------------------------------------------------
<ipython-input-24-c82bc0c1bc22> in <listcomp>(.0)
----> 1 [5.0 / x for x in (1,2,3,4,5,6,0,5,2)]
      2 
ZeroDivisionError: float division by zero

It’s really a pain to have the entire comprehension fail due to one value, particularly if it’s deep in a nested set of iteratables…

Having float.__div__ raise ZeroDivisionError, and using your own function that handles the exception instead of 5.0 / x when you intend to handle division by zero, seems adherent to the Zen of “Explicit is better than implicit.” in my opinion.

The opposite alternative would have you chasing your deep nesting of iterations for which computation originated a non-finite float, when and if you discover at the end of the computation that there is a non-finite float in the results.

2 Likes

I started writing something in the form of a PEP, in case this is deemed to need one. PEP Integral Division - HedgeDoc
There are some missing points here and there, and some minor things not discussed here.

I also realized doing this may open the door to a new feature : a//b returning an accurate integer value greater than the largest float. for example, int(sys.float_info.max)//.5 is int(sys.float_info.max)*2, and that resolves to a valid integer value. Exotic implementation could detect and support division by inverses of integers (and theoretically any number smaller than 1, even though I don’t know how we would implement that). While I don’t think that’s a priority at all, it’s interesting to know that this could become feasible.

The PEP and this thread assume that the purpose of // is to obtain an integer, and see this in isolation.

I do not know what is the intension, but here is an aspect of the role of // that could suggest otherwise.

In the integers (here not referring to int but to \mathbb{Z}) we have Euclidean division. Namely,
given a\in\mathbb{Z} and b\in\mathbb{Z} with b\neq0, there exists a unique pair q,r\in\mathbb{Z} such that

\begin{align*} a&=bq+r\\ 0&\leq r<|b| \end{align*}

We have the same result replacing \mathbb{Z} with \mathbb{R}, real numbers.

Coming back to Python, for a and b instances of int or instances of float we can compute q and r using

q = a // b
r = a % b

The exception ZeroDivisionError stopping us from falling in the case b == 0. There is a difference in Python that a % b doesn’t always give a non-negative r.

Whether or not we have a == b*q + r, I haven’t thought about it, but at least we can test it for all the values for which a // b and a % b can be computed.

If // returns an int, then it is possible to have a and b such that we cannot compute b * q + r. in other words

(a // b) * b + (a % b)

For example, if a is a large int or float and b a small positive float such that a // b is an int that would give OverflowError when converted to float in the multiplication (a // b) * b.

What I describe in the PEP allows, indeed, for implementations to make ints larger than the largest float come out of an integer division. But I’m not saying it’s now required. I think it’s opening the door to that feature and it’s an interesting one, but if you prefer I can move it out of the recommendation section and in an “open-end” section as a regard to the possible future evolutions this enables ?

I don’t think what you say is enough to close the door on generating bigger-than-float ints rather than raising an OverflowError, but at the same time I’m not convinced it definitely should be on the list of things to do and I also think it’s only tangentially related to this question of the return type.
But I certainly feel what you’re saying : I tried making an implementation of the algorithm I drafted near the end, and I simply can’t check the results because then everything overflows all the time !

So, I think I’ll revert the recommendation back to “it should raise OverflowError whenever it used to”.

One other thing to keep in mind that you’ll need to submit a draft PEP besides a sponsor and at least rough consensus here that a PEP is appropriate (even if not agreement on the specifics of the proposal), is you’ll want to have a reference implementation, usually in the form of a PR to the cpython repo or your fork of it, at least as a prototype. Sometimes PEPs are initially published to the PEPs repo with a good reason, or if the author commits to providing one prior to acceptance and we have a basis to trust that, but in general especially in this case IMO it would be best to have at least a rough working prototype prior to merging (and ideally, submitting) a formal draft PEP.

1 Like