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

I agree with you about this, but it IS documented – from the PEP that introduced floor division:

“the result type will be the common type into which a and b are coerced before the operation.”

So yes, it’s documented. But even if it weren’t – I don’t know that every corner case of Python is documented, but this one is not that subtle and has been this way a long time.

  1. the situations it would break anything are marginal : calling .hex, dispatching on the basis of type, and that’s about it.

The other one is the IEEE special values would raise rather than pass through.

No, it’s the kind of landmine we have when we try to apply static typing to s dynamically typed language. I have no idea how MyPy tries to handle the PEP 3141’s Type Hierarchy for Numbers – but apparently not that well (or imperfectly, anyway).

PLEASE don’t make decisions about how Python should be to make it easier to write static type checkers – but if you were going to, then having // always return the same type would make things easeir to static type :slight_smile:

It appears you’re not a fan of PEP 3141 – fine. But it was accepted, so it would be better if Python was consistent.


I have no particular strong opinion about this change, but just on a procedural note, per PEP 1, Final standards-track PEPs themselves at least shouldn’t generally be considered user-facing living, canonical documentation as opposed to historical change proposals, which may not reflect the current state of the implementation:

Once resolution is reached, a PEP is considered a historical document rather than a living specification. Formal documentation of the expected behavior should be maintained elsewhere, such as the Language Reference for core features, the Library Reference for standard library modules or the PyPA Specifications for packaging.

There are some remaining exceptions out of necessity, like Active Process PEPs or specialized interoperability standards like many of the Typing ones that don’t yet have another home, but they don’t really apply in the case of that PEP, as it was purely a core language change proposal that was accepted, implemented and marked Final, and the documentation should be the authoritative, canonical reference here.

The issues are more with the imperfection of the numeric tower itself and PEP 484 favoring practicality over purity (at least the latter of which was likely the best decision in practice) then any particular type checker; in actual use, these issues are relatively rare, and apparently Mypy is working on catching more of these particular corner cases.

Fair point, which is why we need to find a living document that IS canonical.

1 Like

My main takeaway from this discussion is that math.floor etc. are broken in Python3 and should be fixed.

This automatic coercion into a more precise type is an unsafe operation that should not be done without warning, and nonsense:

>>> import math
>>> math.floor(1e23)
1 Like

Not sure what you’re trying to demonstrate here. The floating-point value 1e23 is precisely equal to:

>>> (1e23).as_integer_ratio()
(99999999999999991611392, 1)

So when you floor that value, you get the integer 99999999999999991611392. This seems correct to me. Where’s the nonsense?

1 Like

It seems to me that this particular concern is getting a little off topic for this thread (about floordiv), and rather essentially the same as the one addressed in the previous 1e23 ones (that unfortunately mostly devolved into dumpster fires), which is a separate issue.

Yeah, this has nothing to do with the discussion here.

There are two doc sections relevent here (until another is found, but I doubt it).
Binary arithmetic operations, as quoted by @Rosuav, and Numeric types, table 1, note 1, as quoted by me.

The former one is somewhat more precise in its wording, but I don’t find it that explicit : it only says that “floor division of integers results in an integer” but doesn’t address floor division of floats. Also “the result is that of mathematical division with the ‘floor’ function applied to the result” which, if that sentence applied to the type but I don’t think it does, would imply that float floor division results in an int just as math.floor does. And “The numeric arguments are first converted to a common type”, which doesn’t provide any information as for the return type.
(There are other bits about floor divs but not relevent as to the return type.)

The latter is more in form of a warning : “The resultant value is a whole integer, though the result’s type is not necessarily int.” Nothing more.

So, if we accept @CAM-Gerlach’s explanation on the standing of PEPs, it would make the behavior undocumented. But then :

I think the solution is to give proper warning that it’s about to change, as in a deprecation warning, and to remind (never hurts) that it’s not documented and that it may change again one day (who knows).

I mean deprecation warning in the doc sense, not in the warnings module sense, if we could add an actual warning that would be great but I don’t see how. Maybe on calls to .hex() but it would make a lot of false positives if not called on the result of a floor div.
Maybe inside the floordiv implem so as to emit warnings when floordiv-ing an overflow, inf or nan ? when not float_result.is_integer() ?

Having very strong opinions (“nonsense”) about technology you don’t understand is risky. It’s never stopped me, though, so welcome to the club :slight_smile:

>>> math.floor(1e23) == 1e23

If you think that floating point maths in 2023 is “nonsense”, you should have been around in the 1960s, 1970s and 80s before the IEEE-754 standard was established.

  • Machines where x == y but x/y != 1.0
  • Machines where x != y but x - y == 0.0
  • Machines where 1.0*x != x
  • Other machines where x != 0.0 but y/x crashes with a division by zero error.
  • Yet other machines where 1.0*x can overflow even if x is finite.
  • And my personal favourite, machines where x == y but (x - z) > (y - z).

I believe that all of those anomalies are impossible on IEEE-754 systems.

IEEE-754 floats have many quirks but they are consistent quirks, the same on all machines (ignoring the occasional bug and CPUs that don’t support the standard at all) and unlike past systems, have been carefully designed to be as sensible as possible – even if it doesn’t seem that way at first.

Obligatory link to What Every Computer Scientist Should Know About Floating-Point Arithmetic. For a possibly more understandable explanation, see here.

I believe the reason the second one is more vague is that it’s talking about the entire numeric stack as a whole, hence it attempts to describe what can be seen by numbers in general.

The first reference says this in describing several operations: The numeric arguments are first converted to a common type. By that definition, we should safely be able to assume that, if you divide two values of the same type, the result will be that same type; there’s no conversion necessary to get them to a common type, and then nothing else in the paragraph says anything about conversions.

Oh I am very familiar with these quirks, numerical computation is and has been my job for a long time. The promotion to a more precise an exact type is nonsense (or maybe surprising, if you don’t like the strong wording) and should never be done implicitly. That float 1e23 isn’t int 10**23 was an example to demonstrate the behaviour: you are assigning an exact value to an inexact expression.

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!”.


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.


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!