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

So, coming back to that, as amended.

The issue that I have with that is two-fold.

One, it is not a value “like all others”, since it’s specifically unsupported by the floor, ceil, int and round functions. Just like nan and the opposite infinity. Therefore we can envision these values being specifically unsupported by an operation, just as 0.0 is specifically unsupported by true division. It makes even more sense in the case of nan and infinities than in the case of 0, because of the precedent these four functions create.

Two, the floor division, as returning a float, has something special compared to all other math operations on number types (int, float and complex) : it cannot return all values of its type. For every other operation, there is a way to obtain every single float value (let’s say normalized values to be prudent, but still), by adding or substracting 0, or multiplying or dividing by 1, or multiplying its half by 2…
For floor division, that’s not possible. That’s what places it apart from the others in my view, and what makes it impossible to consider as a “Valid operation between values of a type that yields a result that is a value of [that] type”.

Well, surjectivity is a strange condition to not consider a function an internal operation.

I don’t think it is strange, especially when all returned number values (i.e excluding nan and infinities) can be represented using the int type. Floor div is not similar to the other math operations, it was even originally defined as a composition of true div with a call to a function (floor).
Even modulo, which is the other non-surgective operation (I forgot that one) doesn’t have that property of having all its values be valid ones for another type, and it doesn’t either have that property of being a composition of an operation with a function. Floor div is special.

Also, could you define what you mean precisely by “internal” ?

Internal (n-ary) operation f:X^n\to X, like multiplication of integers. As opposed to External f:X^n\to Y, or f:X^n\times Y\times X^m\to X with Y\neq X, like the scalar (or dot) product, or the multiplication by a scalar.

1 Like

Thanks for the info.
Well, since math.floor is unary and external when applied to float, and since float-float true division is internal, the composition of the two which makes up the floor division makes sense to being external.
f : X x XX
g : XY
f o g : X x XY

So this has petered out – but as it happens, I was coding away today and found myself having to call int(a // b ) over and over again, and purposely using math.ceil() rather than numpy.ceil() (which still returns a float).

I’m still dumbfounded by the resistance to this idea – why are you using floor division if you don’t want an integer?!?

It may be too late to change, sure, but that’s not the same as arguing that this is somehow the best design.

[*] If you are curious, I’m working on some graphics code, where I need integer pixel coords in the end.

1 Like

It depends on what you want to do with the result. If it is a quantity that can be counted (like pixel coordinates), you want an integer. But if it is something that has to be used further in floating point computations, it makes no sense to first convert it to an int and then back to a float. So, there are people like me, who are purposely using numpy.floor in place of math.floor because they do not want the result to be an int.

2 Likes

In pure python, there is no need to “convert back” – you can use an int in pretty much any place you can use a float – that’s the whole point of this thread, and the point of PEP 3141.

Numpy arrays are statically typed, so the trade-offs are different there. Though in most places, the casting rules would work fine there too.

But this thread is not about ceil and floor – that decision was made long ago, it’s about making “floor division” consitent with what ceil and floor already do.

who are purposely using numpy.floor in place of math.floor because they do not want the result to be an int.

In this case, are you using numpy already? If not, how does getting an int result cause practical problems? And how is using numpy.floor easier or better than calling float(math.floor()) ?

And calling numpy.floor is substantially slower for a regular python scalar float value.

In [5]: %timeit float(math.floor(1234.123))
156 ns ± 9.78 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [6]: %timeit np.floor(1234.123)
1 µs ± 125 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Even though there is an extra name lookup and function call.

Anyway, unless a Core Dev is interested in sponsoring a PEP on this, it’s not going anywhere.

If you’re flooring, why do you need the following calculations to be to be “floating point computations” in the first place ?
If you need to multiply afterwards and want that to be a float multiplication to save time, then I’m not sure why the default implementation of an operator should be optimized for uses in the middle of a succession of calculations - specifically float calculations, because if // returns an integer, (a//b)*c would still be optimized, and also more accurate, if c is an int.

I think I’ll try a C implementation, if a PR can make this go forward. Can someone point me to the area of code where such builtin dunders are defined in CPython ?
Nevermind I found it.

I posted an issue on Github. I hesitate to open a pull request : a base implementation could be as simple as that, but it will require at least a big news and changelog entry and some tests changes I expect, not to mention the possible transition steps I explored in the PEP draft (emitting a warning, managing the previously-accepted values…).

In the PEP draf it says

However, the set of int values is a superset of the integer float values - those for which f.is_integer() is True.

This is not taking into account that the underlying sets of values are not all that is important for an algebraic structure, or a type. The operations, their behavior, the algebraic expressions, or logical propositions that they satisfy are also important. If you take the float for which is_integer() returns True, with the operations +, -, and * it is already not a sub-structure of int.

It says

The a//b operation was initially described, in :pep:??? , as being the equivalent of math.floor(a/b).

Perhaps that “equivalent” was only an oversimplification that accidentally got into that text.

It says

It fails (raising an exception) whenever passed 0 as the second operand, as well as when passed float("nan") , float("inf") or float(-inf) as either operand.

I don’t think we want x // y to fail with x finite and y = float('inf') ? The result n is expected to give r = x - y * n where r is x % y. The latter being the remainder operation of floating point numbers, as in page 19.

Note: Shouldn’t the change also change float_divmod for consistency? And well, I already gave my opinion on // returning an int. I have the same opinion of divmod returning a tuple of an int and a float.

I think it is. What operation(s), expression, or logical proposition can you make with an integral-float that you can’t with an int ? As far as I know, only .hex() and direct typechecks.

I think we should reserve this question for until the rest is accepted, but yes, that’s a good point. I wouldn’t want this change to make the same kind of oversight I’m accusing 3141 of.
I’ll note, ironically, that divmod’s documentation describes it (in the case of floats) as “usually math.floor(a / b)”, so even there the return type is not explicit.

I can get behind that, sounds good to me. Though I don’t understand why it sometimes returns -1 instead of 0, but that’s not my problem here.
I’ll integrate that in my draft, I think - separating cases where inf or nan is passed from cases where it’s returned by the true division.

Some examples:

  1. In int the equation x+1=x doesn’t have solutions. In float for which is_integer() returns True (let me call them “fint”) it does have solutions. Note that this example is not about one peculiar algebraic equation. This phenomenon happens for lots of equations.
  2. +, -, * give int outputs for all int inputs. That is not the case for fint.

Your (2) yields a difference in a direct type-check (direct as including isinstance and type/__class__, and excluding duck-typing), so it’s just that it propagates other API (= duck-typed) differences. But it doesn’t add any one situation, expression or behavior to the list of these differences.

Your (1) is a limitation, not a feature you can rely upon. Why do I say that ? If you really need factual points, the fact that direct and exact comparison between floats is advised against in the doc, so testing a+1 == a is as close as it gets from an undocumented behavior.
What’s more, the size of the mantissa and exponent sizes are not constant and not documented, they can be accessed in sys.float_info and change depending on the install, the system, the implementation and who knows what else (as far as the documentation extends), so the values on which the equation is true will change based upon these sizes. If a given value can stop satisfying that equation when moved to another computer, it’s not much more breaking a change if we make it an int and never satisfy the equation ever.

So, these wouldn’t be documented, reliable behavior differences between “fints” (why not) and ints.
I’ll stick with .hex() and direct typechecks.

10 posts were merged into an existing topic: Number theory discussion

Moved the continuing number theory digression to

If you could please continue that discussion over there and focus this one on the concrete merits of the proposal, that would be great, thanks!

1 Like

That is not noise.

That was.


To answer the valid objection, I’ll first note that it’s __truediv__ and not __floatdiv__ as you said or __div__ as I saw earlier in the thread. That was the py2 syntax, obsoleted long before pep 3141.
Where do you get that assertion that “binary operations are always cast to the least precise type” ? Where is it stated as a rule ?

If you’re describing the current behavior of the cpython distrib, no counter-example comes to mind so I’ll assume you’re right. But if it was a solid rule about Python, why would the documentation use only vague and prudent phrasings such as “The resultant value is a whole integer, though the result’s type is not necessarily int.” (ref) ?
Another example : “The numeric arguments are first converted to a common type. (…) Floor division of integers results in an integer; the result is that of mathematical division with the ‘floor’ function applied to the result.” (ref) ? Why wouldn’t the documentation state that rule there, which would be the best place to put it ?

That’s because it’s not a rule. It’s just something that happens to be true in the current builds, and which makes some sense implementation-wise. But if it’s deemed out of place for the operation returning integer values whatever its operands, aka the floor division or “integer division” as nicknamed in the doc, it should not be referred to as something not to break, because there’s no rule to break.

I had decided that I do not care enough to engage any further, but since you quoted me anyway: I deleted that reply because I didn’t put the statement very well – Firstly, I meant __floordiv__() being the binary operation, and secondly, when having multiple floating point types, the result is actually commonly of the widest floating point type.[1] Perhaps it’s better to say that, when inputs of mixed kind (integer, floating) are encountered, they are commonly understood to express real numbers, and cast to an appropriate type. Your findings seem to bear that out, but I accept that maybe it isn’t as fast a rule as I make it out to be.


  1. Specifically, I would generally expect the C standard rules for implicit conversions. ↩︎

1 Like

Thanks for writing this up as a (proto) PEP – I am a supporter of the idee, but even if it’s rejected, it’s really good to have the discussion and conclusion documented.

Is this thread the best way to comment / suggest changes to the PEP draft (PEP Integral Division - HedgeDoc) ? I haven’t found a way to do that on the publishing platform – NOTE: using gitHub or the like has advantages for others being able to make suggestions to the text… Annyway, I’ll do it here for now.

typing recommendations include considering float -typed parameters as accepting int arguments"

Here’s the citation:

And it’s not just a recommendation, it’s built in to what float means in type hints. (at least as I read that – I’m no typing expert)

The int API adds, in addition to supplementary methods, the support for slicing

You should probably explicitly talk about __index__ here.

float s include decimal values and infinities, and int s include values bigger

floats DO NOT support decimal values – they are specifically binary values (that get rounded to decimal for display, etc) – did you mean fractional values?

And I would separate out precision issues from the float special values. They are pretty distinct concepts. Python ints are pretty unique (or special anyway) by being unlimited precision. and, in theory, Python could introduce an unlimited (or variable anyway) precision float type (similar to Decimal).

Which, while I’m at it should be discussed as well, not built in, but it is part of the stdlib, and currently, an_int // a_decimal returns a Decimal type. (like float, natch).

The a//b operation was initially described, in :pep:??? , as
PEP 238: (PEP 238 – Changing the Division Operator | peps.python.org).

The exact quote is:
“”"
Floor division will be implemented in all the Python numeric types, and will have the semantics of:

a // b == floor(a/b)
“”"

The __floordiv__ dunder was however missing in the 3141 Recommendations section, which this PEP seeks to complete.

I think this is THE key point here – in my mind this is clearly an inconsistency – the question is whether it was intentional or not – I have been able to find no documentation that makes that clear. The only place __floordiv__ is mentioned in PEP 3141 is that it “goes away” for the complex type.

Folks who’ve been around a while (@mdickinson @tim.one @guido, … ) – does anyone recall this being discussed as part of the PEP 3141 process?

return an int object whenever they used to return a float with an integer value (on which value.is_integer() is True).

This is a bit confusing to me – but in any case, I think it should specify that, for mixed types, a//b would return:

int(divmod(a, b)[0])

With the same coercion rules as divmod

However, in theory, the implementation could keep the extra precision available in ints through the computation, but I don’t think there’s any easy (and performant) way to do that now.

It seems we could say a//b is exactly floor(a/b) – however, if the truncation is done after the division, rather than all in “float space”, you will get a different result in some cases – got to love FP!:

In [25]: math.floor(6 / .4)
Out[25]: 15

In [26]: 6 // .4
Out[26]: 14.0

(see: Integer division for dicussion and Tim Peter’s excellent explaination)

I’d personally prefer the floor(a / b) version, but that would change the value of the result in these cases :-(.

At least one version of Python has the float methods return int objects, except that it still returns float("nan"), float("inf") or float("-inf") in cases when it used to and when following this PEP would make it raise an exception.

I found the working here a bit confusing – when I first read it, I thought it means that there was a python implementation (e.g. PyPy or micropython, or…) but I now see it’s an optional transition state. It’s an interesting idea – I suppose it’s a question of when the Exception is raised – when the // operation is performed, or when you try to use the result as an integer (e.g. __index__).

The former may be spotted by type checkers

Side note – you may want to say more about typing and type checkers – it seems that Python is moving toward a more “statically typed” stance – I don’t personally like that, but having // always return an int would sure make things easier for type checkers. I actually have no idea if there is a way to type a function like this one, where the return type depends on the input types – but it’s certainly not straightforward. As it’s a builtin, I imagine the type checkers have special cased it, but for mere mortals, it’s pretty tricky to reason about.

This is most likely a small use-case among all uses of floor-division : calculating an integer value, then involving it in further calculations specifically involving floats

I agree here – in “real code” if you care, you could use divmod() – and in any case, the issue is already there with floor() and round() and ceil()

In my experience, I am FAR more likely to apply // or floor() or ceil() as the last step in a computation – but maybe others have a different experience?

Which brings us back to what I think is the main point of this:

It makes floor division consistent with the PEP 3141 number hierarchy. Some on this thread have argued that that was a mistake – fine, but then the response should not be:

“let’s keep this inconsistency”

but rather

“roll back PEP 3141”

Keeping an inconsistency seems like the worst of both worlds.

Ahh – I see in the section " Ramble about overflow …" you do talk a bit about one point above – maybe it should be included (in some form) e.g. “Possible implementation that maintains higher precision”

Finally: I’m not sure how, or if, to express this in the PEP, but as I followed this entire discussion, it struck me that most of the challenges were essentially all the same problems that already exist with FP – just shifted to a slightly different location in the run-time behaviour of the code. There’s no getting around that if you use floats, you have to deal with float issues. If some code happens to work OK without having thought about it (e.g. what happens when you get a , that may be a bad thing, rather than a good one.

Final note: it may be worth mentioning what other implementations of __floordiv__ might do – e.g. numpy. As numpy is more focused on homogenous types (and efficient calculations) it will probably not implement __floordiv__ returning integers, as it doesn’t do for floor() and friends currently:

In [34]: arr6 = np.array([60., 6.0, .6])

In [35]: arr4 = np.array([4, 0.4, 0.04])

In [36]: arr6 / arr4
Out[36]: array([15., 15., 15.])

In [37]: arr6 // arr4
Out[37]: array([15., 14., 14.])

In [38]: np.floor(arr6 / arr4)
Out[38]: array([15., 15., 15.])

It would of course, be up to numpy what to do, but it may be worth mentioning.

1 Like

I added the top recommendations.

By that I meant numbers with points. I can’t say “non-integers” because in context it would be a circular statement, and “real values” seems sketchy for someone not versed into set-based maths. But a more accurate term would be good for sure.

With the implementation being as it stands, there’s not much choice offered to a developper of the Decimal type : it’s closer in purpose and behavior to float than to int, so you calk it on float’s behavior. All in all, it’s the most consistent choice in order to adhere with the inconsistent int//float API.

Yes, though… I also want divmod to return an int, and for the exact same reasons.
PEP 238 you quoted (thanks) says this about how to make //-less integer division : “Use […] divmod(x, y) for int division” (“int division” ! it doesn’t even say “floor division” !).
So, they clearly considered that divmod[0] should return the same as //.
Then, it also says : “If you know your integers are never negative, you can use int(x/y)”, which is music to my ears.

Those are probably covered by the “float arithmetics are clonky” umbrella. As far as I understand Tim Peter’s explanation, the occasional result of 15.0 is due to C implementation details or even system-specific conditions (correct me if I’m wrong).
So, I think it’s okay if a//b is sometimes slightly different to floor(a/b) depending on nebulous conditions that were never documented, never guaranteed, and possibly inconsistent across implems and systems. In any case, a//b would be the same as int(a/b) for positive results, that’s probably a better standard to be consistent with.
I’d refer to the documentation for divmod in float cases, which is almost unreadable if you didn’t major maths but explicitly gives a noncommitment clause for the exact value returned.
And funnily enough, divmod[0] in float cases is described there as being usually math.floor(a / b) :upside_down_face:

If that matters to people, and if some weird edge case uses where float-floordiv is actually useful were to show up, we could always add a math.floordiv function that would return the float.

That’s absolutely true.

Oh, you mean the library which respects dunders so much that __bool__ raises an exception ? (:
If they use // and rely on it returning a float, that’s one thing to consider. Other than that, I think these fine float-managing people would see no problem to continue using float-floordiv.
np.floor returns a float when math.floor returns an int anyway, so I hardly think it would be an issue if np.ndarray.__floordiv__ returns a float and float.__floordiv__ returns an int.