8000 min/max of empty arrays · Issue #5032 · numpy/numpy · GitHub
[go: up one dir, main page]

Skip to content

min/max of empty arrays #5032

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

Closed
bsipocz opened this issue Sep 3, 2014 · 30 comments
Closed

min/max of empty arrays #5032

bsipocz opened this issue Sep 3, 2014 · 30 comments

Comments

@bsipocz
Copy link
Member
bsipocz commented Sep 3, 2014

A similar issue was raised in #2670, however the edge case of (0, ) shape is still not covered. I can go around it with checking for non-zero length in an if statement, but it would be more practical if np.min could handle this, too.

In [24]: [np.min(param[x][1]) for x in fields]
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-24-076aa74e3684> in <module>()
----> 1 [np.min(param[x][1]) for x in fields]

/data/wts/pipeline64/python/lib/python2.6/site-packages/numpy/core/fromnumeric.pyc in amin(a, axis, out)
   1893     except AttributeError:
   1894         return _wrapit(a, 'min', axis, out)
-> 1895     return amin(axis, out)
   1896 
   1897 

ValueError: zero-size array to minimum.reduce without identity

In [25]: param[x]
Out[25]: 
{1: array([], dtype=float64),
 2: array([], dtype=float64),
 3: array([], dtype=float64),
 4: array([], dtype=float64)}

In [26]: param[x][1].shape
Out[26]: (0,)
@seberg
Copy link
Member
seberg commented Sep 3, 2014

gh-2670 is a very different thing. There you calculate the min/max over 2 elements, you just happen to do it 0 times. Here you want to calculate the min/max over 0 elements and that is not well defined.

What is min([])? We can define sum([]) as 0 easily. But -Inf/+Inf for min/max does not seem elegant/clear to be correct. So we raise an error instead at the moment.

@seberg
Copy link
Member
seberg commented Sep 3, 2014

I will close this for now, but please feel free to ask/post more and convince me otherwise :).

@seberg seberg closed this as completed Sep 3, 2014
@bsipocz
Copy link
Member Author
bsipocz commented Sep 3, 2014

OK, I see your point, however can't see why it behaves differently for different empty arrays (e.g. getting the minimum for axis=1 and axis=0 in #2670's example). Both are empty, still the minimum will be either an empty array or a ValueError.
To be honest, I would be most happy with a nan as a result to min([]), at least it seems more reasonable than an error.

In [12]: a = numpy.zeros((0,2))

In [13]: a
Out[13]: array([], shape=(0, 2), dtype=float64)

In [14]: np.min(a, axis=1)
Out[14]: array([], dtype=float64)

In [15]: np.min(a, axis=0)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-15-c8666bcd2a35> in <module>()
----> 1 np.min(a, axis=0)

/data/wts/pipeline64/python/lib/python2.6/site-packages/numpy/core/fromnumeric.pyc in amin(a, axis, out)
   1893     except AttributeError:
   1894         return _wrapit(a, 'min', axis, out)
-> 1895     return amin(axis, out)
   1896 
   1897 

ValueError: zero-size array to minimum.reduce without identity

@pv
Copy link
Member
pv commented Sep 3, 2014

As noted above, the difference is that axis=1 computes minimum over two elements, whereas axis=0 computes it over zero elements. Only the latter has to invent numbers with undefined values as the results.

In principle, inf is the identity for minimum and -inf the identity for maximum, but min([]) == inf may be surprising.

@seberg
Copy link
Member
seberg commented Sep 3, 2014

Maybe to make the difference more clear, compare it with sum:

In [2]: np.ones((2, 0)).sum(0)
Out[2]: array([], dtype=float64)

In [3]: np.ones((2, 0)).sum(1)
Out[3]: array([ 0.,  0.])

In one case, the result is empty itself, in the other case it is not. What happens if you got np.ones((0, 0)).min(0) is philosophical.

Certainly not raising an error but returning something like NaN or Inf might be a reasonable choice, but I think it would be a rather largish change and changing it would have to follow a discussion the mailing list.
Though to be clear, personally I prefer raising the error here, inconvenient for some, but not as surprising for others, and I doubt there will be much will to change it.

@njsmith
Copy link
Member
njsmith commented Sep 3, 2014

-10 on returning Nan (that's just wrong!), -1 on Inf (what do you do
for dtypes that don't have inf?)

and anyway all of the reduction operations behave like this, e.g.:

In [2]: np.divide.reduce([])
ValueError: zero-size array to reduction operation divide which has no identity

@ewmoore
Copy link
Contributor
ewmoore commented Sep 3, 2014

It seems to me that only thing you could really return is an empty array.
But that ship sailed long ago. (And doesn't really seem useful).

@SomeoneSerge
Copy link

I believe it'd be perfectly fine to provide an option to manually specify meaningful identity in a min/max/reduce call.
Like np.zeros((0,0)).min(identity=np.inf) yields infty,
while np.zeros((0,0)).min() can still throw a ValueError or whatever.

@eric-wieser
Copy link
Member
eric-wieser commented Oct 25, 2017

@RerumNovarum : Before anything like that can be implemented, #8955 needs to be merged

@hameerabbasi
Copy link
Contributor
hameerabbasi commented Feb 20, 2018

Repeating from #8955:

I would suggest adding identities to np.minimum and np.maximum, which would be +inf and -inf respectively. This is intuitively and logically true, as max(a, -inf) = a and min(a, +inf) = a.

I agree this probably doesn't match Python conventions but does make sense mathematically. Not to mention, np.nanmin(all-nan) and np.min([]) should be np.inf (Mathematically speaking).

I agree it depends on how you define it, but this is probably the most widely accepted view.

@eric-wieser
Copy link
Member
eric-wieser commented Feb 20, 2018

Your argument for signed inf is valid (indeed, it is probably the best choice other than erroring), but there are valid arguments against it too:

  • min(np.array(data, int16)).dtype == np.int16 must be true even when len(data) == 0 - so returning inf is not possible for integer types. We could return np.iinfo(int16).min, but that would probably be surprising.
  • max(abs(complex_vals)) has a natural identity of 0, not -infinity, but there's no way for max to distinguish that case. Elsewhere a max_abs ufunc has been proposed to solve that problem.

It's worth noting that core python has decided that max([]) should error, and that max([], default=-inf) is the way to spell the behavior you want. Perhaps we should aim for something similar.

@hameerabbasi
Copy link
Contributor
hameerabbasi commented Feb 20, 2018

min(np.array(data, int16)).dtype must return an int16 even when len(data) == 0 - so returning inf is not possible

I guess you could cast and then return whatever is the maximum of int16, which would be valid too (as the universal set is the set of all int16). I guess it comes down to whether we want the maximum or the supremum (the only difference for finite sets being that the supremum would be what I say and and the maximum not well defined for empty sets).

max(abs(complex_vals)) has a natural identity of 0, not -infinity, but there's no way for max to distinguish that case. Elsewhere a max_abs ufunc has been proposed to solve that problem.

I would still argue that the identity value should be -inf in this case, and we should aim for np.max(np.abs(complex_vals), identity=0).

Edit: Or maybe np.maximum(np.max(np.abs(complex_vals)), 0) would also work. I'd argue against adding identity= everywhere because it requires core changes.

@eric-wieser
Copy link
Member
eric-wieser commented Feb 20, 2018

I would still argue that the identity value should be -inf in this case

I think this would be too surprising to be useful. Summarizing the problem:

  • Let X be some set of values
  • Let Y be N samples from X
  • Expectation: min(Y) is in X

The only way this can hold when N == 0, and still satisfy the property of being an identity, is if min([]) == min(X) — but min has no way of know what min(X) is. When X is ℝ, then the answer is -inf. But in general, numpy shouldn't assume the invariants of the set of values the user is passing, even if the assumption is often correct.

To quote the zen of python

In the face of ambiguity, refuse the temptation to guess.

So I'm also -1 on a default for empty arrays.

I'm +1 on allowing np.max(np.abs(complex_vals), identity=0). I though that was blocked by gh-8955, but actually I think it was only blocked by gh-8952 (now merged), so a patch would be welcome!

That patch would look something like adding an identity argument to the C PyUFunc_Reduce function, and using it in place of _get_identity(ufunc) if provided.

@hameerabbasi
Copy link
Contributor
hameerabbasi commented Feb 20, 2018

I'll look into it. I'm not too deep into the annals of core Numpy, if you could point me towards relevant files that'd be a big help. As long as they're Cython/Pure C++ I should be able to do it, but if they're Python API C++... I'm not entirely sure.

Edit: I'm talking about implementing identity=

@eric-wieser
Copy link
Member

The function I mention is defined here

PyUFunc_Reduce(PyUFuncObject *ufunc, PyArrayObject *arr, PyArrayObject *out,
int naxes, int *axes, PyArray_Descr *odtype, int keepdims)
{
int iaxes, ndim;
npy_bool reorderable;
npy_bool axis_flags[NPY_MAXDIMS];
PyArray_Descr *dtype;
PyArrayObject *result;
PyObject *identity = NULL;
const char *ufunc_name = ufunc_get_name_cstr(ufunc);
/* These parameters come from a TLS global */
int buffersize = 0, errormask = 0;
NPY_UF_DBG_PRINT1("\nEvaluating ufunc %s.reduce\n", ufunc_name);
ndim = PyArray_NDIM(arr);
/* Create an array of flags for reduction */
memset(axis_flags, 0, ndim);
for (iaxes = 0; iaxes < naxes; ++iaxes) {
int axis = axes[iaxes];
if (axis_flags[axis]) {
PyErr_SetString(PyExc_ValueError,
"duplicate value in 'axis'");
return NULL;
}
axis_flags[axis] = 1;
}
if (_get_bufsize_errmask(NULL, "reduce", &buffersize, &errormask) < 0) {
return NULL;
}
/* Get the identity */
identity = _get_identity(ufunc, &reorderable);
if (identity == NULL) {
return NULL;
}
/*
* The identity for a dynamic dtype like
* object arrays can't be used in general
*/
if (identity != Py_None && PyArray_ISOBJECT(arr) && PyArray_SIZE(arr) != 0) {
Py_DECREF(identity);
identity = Py_None;
Py_INCREF(identity);
}
/* Get the reduction dtype */
if (reduce_type_resolver(ufunc, arr, odtype, &dtype) < 0) {
Py_DECREF(identity);
return NULL;
}
result = PyUFunc_ReduceWrapper(arr, out, NULL, dtype, dtype,
NPY_UNSAFE_CASTING,
axis_flags, reorderable,
keepdims, 0,
identity,
reduce_loop,
ufunc, buffersize, ufunc_name, errormask);
Py_DECREF(dtype);
Py_DECREF(identity);
return result;
}

And called here:

/*
* This code handles reduce, reduceat, and accumulate
* (accumulate and reduce are special cases of the more general reduceat
* but they are handled separately for speed)
*/
static PyObject *
PyUFunc_GenericReduction(PyUFuncObject *ufunc, PyObject *args,
PyObject *kwds, int operation)
{
int i, naxes=0, ndim;
int axes[NPY_MAXDIMS];
PyObject *axes_in = NULL;
PyArrayObject *mp = NULL, *ret = NULL;
PyObject *op, *res = NULL;
PyObject *obj_ind, *context;
PyArrayObject *indices = NULL;
PyArray_Descr *otype = NULL;
PyArrayObject *out = NULL;
int keepdims = 0;
static char *reduce_kwlist[] = {
"array", "axis", "dtype", "out", "keepdims", NULL};
static char *accumulate_kwlist[] = {
"array", "axis", "dtype", "out", NULL};
static char *reduceat_kwlist[] = {
"array", "indices", "axis", "dtype", "out", NULL};
static char *_reduce_type[] = {"reduce", "accumulate", "reduceat", NULL};
if (ufunc == NULL) {
PyErr_SetString(PyExc_ValueError, "function not supported");
return NULL;
}
if (ufunc->core_enabled) {
PyErr_Format(PyExc_RuntimeError,
"Reduction not defined on ufunc with signature");
return NULL;
}
if (ufunc->nin != 2) {
PyErr_Format(PyExc_ValueError,
"%s only supported for binary functions",
_reduce_type[operation]);
return NULL;
}
if (ufunc->nout != 1) {
PyErr_Format(PyExc_ValueError,
"%s only supported for functions "
"returning a single value",
_reduce_type[operation]);
return NULL;
}
/* if there is a tuple of 1 for `out` in kwds, unpack it */
if (kwds != NULL) {
PyObject *out_obj = PyDict_GetItem(kwds, npy_um_str_out);
if (out_obj != NULL && PyTuple_CheckExact(out_obj)) {
if (PyTuple_GET_SIZE(out_obj) != 1) {
PyErr_SetString(PyExc_ValueError,
"The 'out' tuple must have exactly one entry");
return NULL;
}
out_obj = PyTuple_GET_ITEM(out_obj, 0);
PyDict_SetItem(kwds, npy_um_str_out, out_obj);
}
}
if (operation == UFUNC_REDUCEAT) {
PyArray_Descr *indtype;
indtype = PyArray_DescrFromType(NPY_INTP);
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO&O&:reduceat", reduceat_kwlist,
&op,
&obj_ind,
&axes_in,
PyArray_DescrConverter2, &otype,
PyArray_OutputConverter, &out)) {
goto fail;
}
indices = (PyArrayObject *)PyArray_FromAny(obj_ind, indtype,
1, 1, NPY_ARRAY_CARRAY, NULL);
if (indices == NULL) {
goto fail;
}
}
else if (operation == UFUNC_ACCUMULATE) {
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OO&O&:accumulate",
accumulate_kwlist,
&op,
&axes_in,
PyArray_DescrConverter2, &otype,
PyArray_OutputConverter, &out)) {
goto fail;
}
}
else {
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OO&O&i:reduce",
reduce_kwlist,
&op,
&axes_in,
PyArray_DescrConverter2, &otype,
PyArray_OutputConverter, &out,
&keepdims)) {
goto fail;
}
}
/* Ensure input is an array */
if (!PyArray_Check(op) && !PyArray_IsScalar(op, Generic)) {
context = Py_BuildValue("O(O)i", ufunc, op, 0);
}
else {
context = NULL;
}
mp = (PyArrayObject *)PyArray_FromAny(op, NULL, 0, 0, 0, context);
Py_XDECREF(context);
if (mp == NULL) {
goto fail;
}
ndim = PyArray_NDIM(mp);
/* Check to see that type (and otype) is not FLEXIBLE */
if (PyArray_ISFLEXIBLE(mp) ||
(otype && PyTypeNum_ISFLEXIBLE(otype->type_num))) {
PyErr_Format(PyExc_TypeError,
"cannot perform %s with flexible type",
_reduce_type[operation]);
goto fail;
}
/* Convert the 'axis' parameter into a list of axes */
if (axes_in == NULL) {
naxes = 1;
axes[0] = 0;
}
/* Convert 'None' into all the axes */
else if (axes_in == Py_None) {
naxes = ndim;
for (i = 0; i < naxes; ++i) {
axes[i] = i;
}
}
else if (PyTuple_Check(axes_in)) {
naxes = PyTuple_Size(axes_in);
if (naxes < 0 || naxes > NPY_MAXDIMS) {
PyErr_SetString(PyExc_ValueError,
"too many values for 'axis'");
goto fail;
}
for (i = 0; i < naxes; ++i) {
PyObject *tmp = PyTuple_GET_ITEM(axes_in, i);
int axis = PyArray_PyIntAsInt(tmp);
if (error_converting(axis)) {
goto fail;
}
if (check_and_adjust_axis(&axis, ndim) < 0) {
goto fail;
}
axes[i] = (int)axis;
}
}
/* Try to interpret axis as an integer */
else {
int axis = PyArray_PyIntAsInt(axes_in);
/* TODO: PyNumber_Index would be good to use here */
if (error_converting(axis)) {
goto fail;
}
/* Special case letting axis={0 or -1} slip through for scalars */
if (ndim == 0 && (axis == 0 || axis == -1)) {
axis = 0;
}
else if (check_and_adjust_axis(&axis, ndim) < 0) {
goto fail;
}
axes[0] = (int)axis;
naxes = 1;
}
/* Check to see if input is zero-dimensional. */
if (ndim == 0) {
/*
* A reduction with no axes is still valid but trivial.
* As a special case for backwards compatibility in 'sum',
* 'prod', et al, also allow a reduction where axis=0, even
* though this is technically incorrect.
*/
naxes = 0;
if (!(operation == UFUNC_REDUCE &&
(naxes == 0 || (naxes == 1 && axes[0] == 0)))) {
PyErr_Format(PyExc_TypeError, "cannot %s on a scalar",
_reduce_type[operation]);
goto fail;
}
}
/*
* If out is specified it determines otype
* unless otype already specified.
*/
if (otype == NULL && out != NULL) {
otype = PyArray_DESCR(out);
Py_INCREF(otype);
}
if (otype == NULL) {
/*
* For integer types --- make sure at least a long
* is used for add and multiply reduction to avoid overflow
*/
int typenum = PyArray_TYPE(mp);
if ((PyTypeNum_ISBOOL(typenum) || PyTypeNum_ISINTEGER(typenum))
&& ((strcmp(ufunc->name,"add") == 0)
|| (strcmp(ufunc->name,"multiply") == 0))) {
if (PyTypeNum_ISBOOL(typenum)) {
typenum = NPY_LONG;
}
else if ((size_t)PyArray_DESCR(mp)->elsize < sizeof(long)) {
if (PyTypeNum_ISUNSIGNED(typenum)) {
typenum = NPY_ULONG;
}
else {
typenum = NPY_LONG;
}
}
}
otype = PyArray_DescrFromType(typenum);
}
switch(operation) {
case UFUNC_REDUCE:
ret = PyUFunc_Reduce(ufunc, mp, out, naxes, axes,
otype, keepdims);
break;
case UFUNC_ACCUMULATE:
if (naxes != 1) {
PyErr_SetString(PyExc_ValueError,
"accumulate does not allow multiple axes");
goto fail;
}
ret = (PyArrayObject *)PyUFunc_Accumulate(ufunc, mp, out, axes[0],
otype->type_num);
break;
case UFUNC_REDUCEAT:
if (naxes != 1) {
PyErr_SetString(PyExc_ValueError,
"reduceat does not allow multiple axes");
goto fail;
}
ret = (PyArrayObject *)PyUFunc_Reduceat(ufunc, mp, indices, out,
axes[0], otype->type_num);
Py_DECREF(indices);
break;
}
Py_DECREF(mp);
Py_DECREF(otype);
if (ret == NULL) {
return NULL;
}
/* If an output parameter was provided, don't wrap it */
if (out != NULL) {
return (PyObject *)ret;
}
if (Py_TYPE(op) != Py_TYPE(ret)) {
res = PyObject_CallMethod(op, "__array_wrap__", "O", ret);
if (res == NULL) {
PyErr_Clear();
}
else if (res == Py_None) {
Py_DECREF(res);
}
else {
Py_DECREF(ret);
return res;
}
}
return PyArray_Return(ret);
fail:
Py_XDECREF(otype);
Py_XDECREF(mp);
return NULL;
}

You'd need to:

  • Parse a identity kwarg for reduce in one of the PyArg_ParseTupleAndKeywords calls, and call _get_identity if the argument is missing.
  • Pass it to PyUFunc_Reduce, rather than having PyUFunc_Reduce compute it internally
  • Add tests and documentation

@hameerabbasi
Copy link
Contributor

Why leave it at reduce when reduceat, accumulate and accumulateat, and NaN-aggregations would also need it? This is rather big, but I'm willing to take it on. 😄

@eric-wieser
Copy link
Member
  • reduceat doesn't even use it's identity - don't open that can of worms. There's another issue about that elsewhere
  • accumulate perhaps doesn't need it? np.minimum.accumulate([]) is already well-defined
  • accumulateat doesn't exist

@hameerabbasi
Copy link
Contributor

Can you reopen the issue?

@seberg
Copy link
Member
seberg commented Apr 26, 2018

why reopen? Having an initial seems like the only thing to do here for sure?

@seberg
Copy link
Member
seberg commented Apr 26, 2018

Ooops misread, har :)

@carlosgmartin
Copy link
Contributor

FWIW, I agree with @hameerabbasi. Just as the

  • sum of an empty array is the identity element of addition, which is zero
  • prod of an empty array is the identity element of multiplication, which is one

It ought to be the case that the

  • max of an empty array is the identity element of max, which is the least element of its dtype
  • min of an empty array is the identity element of min, which is greatest element of its dtype

This is the mathematically correct way to treat the supremum/infimum of an empty collection.

Related: jax-ml/jax#18661.

@jakevdp
Copy link
Contributor
jakevdp commented Nov 28, 2023

Echoing some of my thoughts from jax-ml/jax#18661 here in case it is helpful:

The problem with using the identity for min/max is that the supremum and infimum are only well-defined for bounded sets. Real numbers and integers are unbounded sets, so the supremum and infimum are not defined. While it's true that fixed-width approximations to these sets (like the sets of values representable by float64 or int64) are bounded, it would be confusing to use their supremum or infimum as identities of min and max, because these are properties of the implementation, not properties of the numbers that the implementation represents (and no, $\infty$ is not a real number 😁).

Contrast this with sum/prod, in which the identities are well-defined for integers and real numbers, so it makes sense to return those identities in the case of a reduction over an empty array.

All that to say, I think the current approach used by both NumPy and Python (where min/max of empty sequences errors, unless an explicit identity is provided) is the right default behavior.

@rkern
Copy link
Member
rkern commented Nov 28, 2023

FWIW, IEEE 754 floating point numbers model the affinely extended reals more so than the reals, per se, and -∞ and +∞ are perfectly fine members and indeed the well-defined infimum and supremum identity values for min and max.

While I don't think it's necessary to define the result of min and max on empty typed arrays like that, I think it'd be valid and no more confusing than any of the other places where we use the properties of the specific dtype. For the integer dtypes, the finite bounds come into play elsewhere; I'm not sure why it would be confusing to use them in a mathematical place where such bounds are quite salient.

@jakevdp
Copy link
Contributor
jakevdp commented Nov 28, 2023

OK, fair enough. But I do think it would be quite surprising and confusing to the majority of NumPy users if np.arange(0).min() were to return 9223372036854775807.

@SomeoneSerge
Copy link
SomeoneSerge commented Nov 28, 2023

+1 to throwing an exception but accepting an optional identity=; I forget what my use-case was at the time, but there were values that made sense in the specific context.

We don't have to make assumptions about how exactly the end users interpret their numbers, so let's just not

@jakevdp
Copy link
Contributor
jakevdp commented Nov 28, 2023

+1 to throwing an exception but accepting an optional identity=

Note that the identity argument is already available as of NumPy 1.15 (but it's called initial):

>>> np.arange(0).min(initial=np.iinfo('int64').max)
9223372036854775807

@carlosgmartin
Copy link
Contributor
carlosgmartin commented Nov 28, 2023

A possible compromise / feature that occurs to me is adding .max and .min attributes / properties directly to dtypes, instead of alternating between iinfo, finfo, and a special case for bool, depending on the type.

a.max(initial=a.dtype.min)

From an API design POV, it seems to me that the common fields of the objects returned by iinfo and finfo (min, max, bits) ought to be accessible via the same interface.

@seberg
Copy link
Member
seberg commented Nov 29, 2023

A possible compromise / feature that occurs to me is adding .max and .min attributes

I have made a lot of work to allow adding such attributes to the dtype instances (not sure I like min/max, it feels like .info.min or just something longer may be better).
In fact, I always thought that is where finfo/iinfo must move long term. So I would welcome work for going towards that.

but it's called initial

Right, just to note that the name is of course intentional, because identity hints to unrelated properties. Also I want(ed) the freedom to add a default= which is not used as the initial value for the reduce-like operation but returned for empty results. That would allow default=np.nan to return NaN for empty min.


Besides boolean, where I guess it is fully unambigious. It seems to me that we will remain at: yes, it is plausible to use ±inf, but it is unclear that it is better.

I do think both are valid choices, but it still seems like more guesswork to me than others (like mean([]) returning NaN, because it does sum([])/0 = 0/0).

Probably the reason is that within many contexts the actual minimum value may be well defined, e.g. 0. Also, I would think you should give an "infinity created" (division by zero) warning, which would be tedious ;).

@carlosgmartin
Copy link
Contributor

A possible compromise / feature that occurs to me is adding .max and .min attributes

I have made a lot of work to allow adding such attributes to the dtype instances (not sure I like min/max, it feels like .info.min or just something longer may be better). In fact, I always thought that is where finfo/iinfo must move long term. So I would welcome work for going towards that.

I'm interested in a uniform interface for accessing the minimum/maximum value of a given dtype (JAX example). Is there an open issue discussing this? If not, I could open one.

@seberg
Copy link
Member
seberg commented Aug 24, 2024

Not sure, #27231 is somewhat related, though. You can open a new one and even work on it if you like.

(I am not sure how simple it would be to add these attributes. It might be very weedy in C, but actually, I think it may just be possible to do it in np.dtypes. in Python. In which case the only complicated thing is to make the decision.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

0