Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: Support integer dtype inputs in rounding functions #26766

Merged
merged 1 commit into from
Jul 23, 2024

Conversation

mtsokol
Copy link
Member

@mtsokol mtsokol commented Jun 20, 2024

Hi @seberg @ngoldbaum,

This PR adds support for integer dtype inputs in floor, ceil, and trunc ufuncs to match the Array API standard.

For integer dtype inputs an identity is returned in a loop instead of casting to the float dtype.

@mtsokol mtsokol self-assigned this Jun 20, 2024
@mtsokol mtsokol changed the title ENH: Support integer dtype inputs in rounding functions. ENH: Support integer dtype inputs in rounding functions Jun 20, 2024
@mtsokol mtsokol added this to the 2.1.0 release milestone Jun 21, 2024
@jakevdp
Copy link
Contributor

jakevdp commented Jun 24, 2024

What about booleans? The Array API spec doesn't mention these here, but it seems like it would be consistent to return boolean outputs for boolean inputs to these functions.

@seberg
Copy link
Member

seberg commented Jun 25, 2024

I suppose we have to make a choice for them, and it could go three ways:

  1. round is math, and math behaves like integers, as it does for Python for better or worse.
  2. We just raise, because rounding booleans is weird. (I.e. a normal deprecation.)
  3. Propagate booleans because it is nice to be able to no-op on non-floats.

But if you propagate, does round(True, -1) actually return False? I am not sure what I prefer, but since I doubt we will change True + True in NumPy, maybe just going to the default integer is actually pretty sane?

@mattip
Copy link
Member

mattip commented Jun 26, 2024

round is not one of the ufuncs we are dealing with here. Only floor, ceil and trunc. So in that case can we make both bool and int no-ops here? Or am I missing a corner case.

@mtsokol
Copy link
Member Author

mtsokol commented Jun 28, 2024

I think no-op support for bool dtype also makes sense to me.

@@ -61,6 +61,17 @@ NPY_NO_EXPORT void NPY_CPU_DISPATCH_CURFX(@TYPE@_reciprocal)
UNARY_LOOP_FAST(@type@, @type@, *out = 1.0 / in);
}

/**begin repeat1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a major worry, but just wondering: Aren't there loops for np.positive already? Is it possible to reuse those rather than define new ones?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's a loop per each dtype and function, like @TYPE@_@kind@.

Here, for positive we have:

NPY_NO_EXPORT void NPY_CPU_DISPATCH_CURFX(@TYPE@_positive)
(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(func))
{
    UNARY_LOOP_FAST(@type@, @type@, *out = +in);
}

I think for floor, ceil, and trunc we need separate ones anyway to follow the convention dtype_func. Or do you mean changing @TYPE@_positive to @TYPE@_@kind@ and adding an inner repeat1 loop here? (with #kind = floor, ceil, trunc, positive#)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just add an alias define to the header file.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, an alias seems the right solution, to avoid compiling the same code four times!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! I added #define alias pointing to positive loops for floor, ceil, and trunc ufuncs.

But this alias covers only ints, as positive doesn't support boolean dtype. For boolean dtype I added separate no-op loops (I didn't find other ufunc that would be a no-op for booleans), is it OK?

Copy link
Member Author

@mtsokol mtsokol Jul 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure! But raising would be a new backward incompatible behavior. Then I propose to leave bool and continue to cast (floor, ceil, and trunc cast bool dtype right now in main). Is it correct?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see what you mean, I hadn't quite realized currently it cast to float. If you do nothing about bool in your PR, does it still cast to float, or does it become int? If we don't pass on bool or raise, I prefer casting to int to casting to float...

If this becomes unclear, one way out is simply not to touch it here, but raise a separate issue to discuss, hoping still to decide before 5.1.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would go to the first type that matches, which is either uint8 or int8 (dunno).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It casts to int8:

In [1]: np.ceil(np.array([1, 0, 1]).astype(bool))
Out[1]: array([1, 0, 1], dtype=int8)

Then let me update it here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! Ready from my side!

@mtsokol mtsokol force-pushed the rounding-dtypes branch 4 times, most recently from 93d4ebe to c084f64 Compare July 1, 2024 13:50
@mtsokol
Copy link
Member Author

mtsokol commented Jul 1, 2024

It's ready for another review! (I think CI failures are unrelated as they occur in other PRs as well.)

@mtsokol mtsokol force-pushed the rounding-dtypes branch 2 times, most recently from 9f625d8 to 52792d4 Compare July 2, 2024 19:39
@seberg
Copy link
Member

seberg commented Jul 3, 2024

@mhvk, we discussed this a bit and slightly leaned towards pass through being maybe the simplest (even if we don't touch positive)...

I'll say that I might slightly prefer just deprecating it, but I just don't think it matters much and I won't have squirms with deprecating it later.

I do not like the current int8, but would be just as happy (maybe even more so?) with pass through as default integer, i.e. ??->n loop, that at least aligns with math.ceil(True).


There are a lot more float only functions with similar wonky behavior for ints and bools. However, for most of them, the return dtype should just be changed to promote to float64.

@mhvk
Copy link
Contributor

mhvk commented Jul 3, 2024

@seberg - I don't really mind much either way, just felt the easiest way to get this (nice) addition in was not to worry about bool at all in this PR, and discuss separately. If we are going to deal with it here, then, given that np.negative and np.positive both do not work, my very slight preference would be to just bail (i.e., deprecate), but, really, I do not care as I think in the end this is a corner case that will happen essentially never.

@mtsokol
Copy link
Member Author

mtsokol commented Jul 4, 2024

@seberg I added no-op for boolean dtype - it's completed from my side.

Copy link
Member

@ngoldbaum ngoldbaum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me given the decision to make the bool ops a no-op. Also nice and simple.

@mattip mattip merged commit 5cf7883 into numpy:main Jul 23, 2024
67 of 68 checks passed
@mattip
Copy link
Member

mattip commented Jul 23, 2024

Thanks @mtsokol

@mtsokol mtsokol deleted the rounding-dtypes branch July 23, 2024 07:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants