[Numpy-discussion] Floor divison on int returns float

Nathaniel Smith njs at pobox.com
Wed Apr 13 16:48:58 EDT 2016


On Apr 13, 2016 9:08 AM, "Robert Kern" <robert.kern at gmail.com> wrote:
>
> On Wed, Apr 13, 2016 at 3:17 AM, Antony Lee <antony.lee at berkeley.edu> wrote:
> >
> > This kind of issue (see also https://github.com/numpy/numpy/issues/3511) has become more annoying now that indexing requires integers (indexing with a float raises a VisibleDeprecationWarning).  The argument "dividing an uint by an int may give a result that does not fit in an uint nor in an int" does not sound very convincing to me,
>
> It shouldn't because that's not the rule that numpy follows. The range of the result is never considered. Both *inputs* are cast to the same type that can represent the full range of either input type (for that matter, the actual *values* of the inputs are also never considered). In the case of uint64 and int64, there is no really good common type (the integer hierarchy has to top out somewhere), but float64 merely loses resolution rather than cutting off half of the range of uint64.

Let me play devil's advocate for a moment, since I've just been
playing out this debate in my own mind and you've done a good job of
articulating the case for that side :-).

The counter argument is: it doesn't really matter about having a
common type or not; what matters is whether the operation can be
defined sensibly. For uint64 <op> int64, this is actually not a
problem: we provide 2s complement signed ints, so uint64 and int64 are
both integers-mod-2**64, just choosing different representatives for
the equivalence classes in the upper half of the ring. In particular,
the uint64 and int64 ranges are isomorphic to each other.

or with less jargon: casting between uint64 and int64 commutes with
all arithmetic operations, so you actually get the same result
performing the operation in infinite precision and then casting to
uint64 or int64, or casting both operations to uint64 or int64 and
then casting the result to uint64 or int64. Basically the operations
are totally well-defined even if we stick within integers, and the
casting is just another form of integer wraparound; we're already
happy to tolerate wraparound for int64 <op> int64 or uint64 <op>
uint64, so it's not entirely clear why we go all the way to float to
avoid it for uint64 <op> int64.

[On second thought... I'm actually not 100% sure that the
all-operations-commute-with-casting thing is true in the case of //'s
rounding behavior. I would have to squint a lot to figure that out. I
guess comparison operations are another exception -- a < b !=
np.uint64(a) < np.uint64(b) in general.]

-n



More information about the NumPy-Discussion mailing list