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

gh-126061: Add PyLong_IsPositive/Zero/Negative() functions #126065

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
33 changes: 33 additions & 0 deletions Doc/c-api/long.rst
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,39 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
.. versionadded:: 3.14


.. c:function:: int PyLong_IsPositive(PyObject *obj)

Check if the integer object *obj* is positive.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Check if the integer object *obj* is positive.
Check if the integer object *obj* is strictly positive.

Maybe add:

   Check if the integer object *obj* is strictly positive: if *obj* > ``0``.

Copy link
Member

Choose a reason for hiding this comment

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

I think there is no ambiguity. Positive is for, well, being strictly positive. Else we would use something like "nonnegative".

Copy link
Member

Choose a reason for hiding this comment

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

For me, zero is a positive number. "strictly positive" is used in a few places in Python:

Modules/_bz2module.c:    /* data_size is strictly positive, but because we repeatedly have to
Modules/_io/bufferedio.c:            "buffer size must be strictly positive");
Modules/_io/textio.c:                        "a strictly positive integer is required");
Modules/zlibmodule.c:    /* data_size is strictly positive, but because we repeatedly have to
Python/gc.c:   objects that can be reached directly from outside must have strictly positive
Python/gc.c:   by the reachable objects (the ones with strictly positive reference count).
Python/sysmodule.c:                        "switch interval must be strictly positive");

An alternative is to say "Check if the integer is greater than zero".

Copy link
Member

Choose a reason for hiding this comment

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

For me, zero is a positive number.

Uh, it doubt I'm able to find math source with this meaning. But a lot for a different opinion:
https://mathworld.wolfram.com/PositiveInteger.html
https://mathworld.wolfram.com/PositiveNumber.html
https://en.wikipedia.org/wiki/Integer

and

$ git grep positive Modules/mathmodule.c Modules/cmathmodule.c|wc -l
11

There is another story: whether 0 is a natural number or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've applied this advice to positive and negative. Maybe that's more appropriate. Maybe victor meant it more purely (it's very positive💯).

Copy link
Member

Choose a reason for hiding this comment

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

it's very positive

Also it's very negative:D

Lets just stick with math terminolgy.

Copy link
Member

Choose a reason for hiding this comment

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

@rruuaanng, please don't mark unresolved conversations as resolved.

Copy link
Member

Choose a reason for hiding this comment

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

If you dislike strictly, I suggest:

Suggested change
Check if the integer object *obj* is positive.
Check if the integer object *obj* is positive: greater than zero.

Copy link
Member

Choose a reason for hiding this comment

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

I don't feel this addition is needed.

But at least this form is less confusing than "strictly positive". Maybe: "Check if the integer object obj is positive, i.e. greater than zero."


If *obj* is an instance of :c:type:`PyLongObject` or it's subtype,
return ``1`` when it's positive and ``0`` otherwise. Else set an
exception and return ``-1``.

.. versionadded:: next


.. c:function:: int PyLong_IsNegative(PyObject *obj)

Check if the integer object *obj* is negative.

If *obj* is an instance of :c:type:`PyLongObject` or it's subtype,
return ``1`` when it's negative and ``0`` otherwise. Else set an
exception and return ``-1``.

.. versionadded:: next


.. c:function:: int PyLong_IsZero(PyObject *obj)

Check if the integer object *obj* is zero.

If *obj* is an instance of :c:type:`PyLongObject` or it's subtype,
return ``1`` when it's zero and ``0`` otherwise. Else set an
exception and return ``-1``.

.. versionadded:: next


.. c:function:: PyObject* PyLong_GetInfo(void)

On success, return a read only :term:`named tuple`, that holds
Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,11 @@ New Features
an interned string and deallocate it during module shutdown.
(Contribued by Eddie Elizondo in :gh:`113601`.)

* Add :c:func:`PyLong_IsPositive`, :c:func:`PyLong_IsNegative`
and :c:func:`PyLong_IsZero` for checking if :c:type:`PyLongObject`
is positive, negative, or zero.
rruuaanng marked this conversation as resolved.
Show resolved Hide resolved
(Contribued by James Roy and Sergey B Kirpichev in :gh:`126061`.)

* Add new functions to convert C ``<stdint.h>`` numbers from/to Python
:class:`int`:

Expand Down
18 changes: 18 additions & 0 deletions Include/cpython/longobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,24 @@ PyAPI_FUNC(PyObject*) PyLong_FromUnsignedNativeBytes(const void* buffer,
PyAPI_FUNC(int) PyUnstable_Long_IsCompact(const PyLongObject* op);
PyAPI_FUNC(Py_ssize_t) PyUnstable_Long_CompactValue(const PyLongObject* op);

/* PyLong_IsPositive. Check if the integer object is positive.
- On success, return 1 if *obj is positive, and 0 otherwise.
- On failure, set an exception, and return -1. */
PyAPI_FUNC(int) PyLong_IsPositive(PyObject *obj);

/* PyLong_IsNegative. Check if the integer object is negative.
- On success, return 1 if *obj is negative, and 0 otherwise.
- On failure, set an exception, and return -1. */
PyAPI_FUNC(int) PyLong_IsNegative(PyObject *obj);

/* PyLong_IsZero. Check if the integer object is zero.
- On success, return 1 if *obj is zero, and 0 if it is non-zero.
- On failure, set an exception, and return -1. */
PyAPI_FUNC(int) PyLong_IsZero(PyObject *obj);
skirpichev marked this conversation as resolved.
Show resolved Hide resolved

/* PyLong_GetSign. Get the sign of an integer object:
0, -1 or +1 for zero, negative or positive integer, respectively.
Expand Down
45 changes: 45 additions & 0 deletions Lib/test/test_capi/test_long.py
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,51 @@ def test_long_getsign(self):

# CRASHES getsign(NULL)

def test_long_ispositive(self):
rruuaanng marked this conversation as resolved.
Show resolved Hide resolved
# Test PyLong_IsPositive()
ispositive = _testcapi.pylong_ispositive
self.assertEqual(ispositive(1), 1)
self.assertEqual(ispositive(123), 1)
self.assertEqual(ispositive(-1), 0)
self.assertEqual(ispositive(0), 0)
self.assertEqual(ispositive(True), 1)
self.assertEqual(ispositive(False), 0)
self.assertEqual(ispositive(IntSubclass(-1)), 0)
self.assertRaises(TypeError, ispositive, 1.0)
self.assertRaises(TypeError, ispositive, Index(123))
rruuaanng marked this conversation as resolved.
Show resolved Hide resolved
ZeroIntensity marked this conversation as resolved.
Show resolved Hide resolved

# CRASHES ispositive(NULL)

def test_long_isnegative(self):
rruuaanng marked this conversation as resolved.
Show resolved Hide resolved
# Test PyLong_IsNegative()
isnegative = _testcapi.pylong_isnegative
self.assertEqual(isnegative(1), 0)
self.assertEqual(isnegative(123), 0)
self.assertEqual(isnegative(-1), 1)
self.assertEqual(isnegative(0), 0)
self.assertEqual(isnegative(True), 0)
self.assertEqual(isnegative(False), 0)
self.assertEqual(isnegative(IntSubclass(-1)), 1)
self.assertRaises(TypeError, isnegative, 1.0)
self.assertRaises(TypeError, isnegative, Index(123))
rruuaanng marked this conversation as resolved.
Show resolved Hide resolved

# CRASHES isnegative(NULL)

def test_long_iszero(self):
rruuaanng marked this conversation as resolved.
Show resolved Hide resolved
# Test PyLong_IsZero()
iszero = _testcapi.pylong_iszero
self.assertEqual(iszero(1), 0)
self.assertEqual(iszero(-1), 0)
self.assertEqual(iszero(0), 1)
rruuaanng marked this conversation as resolved.
Show resolved Hide resolved
self.assertEqual(iszero(True), 0)
self.assertEqual(iszero(False), 1)
self.assertEqual(iszero(IntSubclass(-1)), 0)
self.assertEqual(iszero(IntSubclass(0)), 1)
self.assertRaises(TypeError, iszero, 1.0)
self.assertRaises(TypeError, iszero, Index(123))
rruuaanng marked this conversation as resolved.
Show resolved Hide resolved

# CRASHES iszero(NULL)

def test_long_asint32(self):
# Test PyLong_AsInt32() and PyLong_FromInt32()
to_int32 = _testlimitedcapi.pylong_asint32
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add :c:func:`PyLong_IsPositive`, :c:func:`PyLong_IsNegative`
and :c:func:`PyLong_IsZero` for checking if :c:type:`PyLongObject`
is positive, negative, or zero.
rruuaanng marked this conversation as resolved.
Show resolved Hide resolved
27 changes: 27 additions & 0 deletions Modules/_testcapi/long.c
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,30 @@ pylong_getsign(PyObject *module, PyObject *arg)
}


static PyObject *
pylong_ispositive(PyObject *module, PyObject *arg)
{
NULLABLE(arg);
RETURN_INT(PyLong_IsPositive(arg));
rruuaanng marked this conversation as resolved.
Show resolved Hide resolved
}


static PyObject *
pylong_isnegative(PyObject *module, PyObject *arg)
{
NULLABLE(arg);
RETURN_INT(PyLong_IsNegative(arg));
}


static PyObject *
pylong_iszero(PyObject *module, PyObject *arg)
{
NULLABLE(arg);
RETURN_INT(PyLong_IsZero(arg));
}


static PyObject *
pylong_aspid(PyObject *module, PyObject *arg)
{
Expand All @@ -124,6 +148,9 @@ static PyMethodDef test_methods[] = {
{"pylong_fromnativebytes", pylong_fromnativebytes, METH_VARARGS},
{"pylong_getsign", pylong_getsign, METH_O},
{"pylong_aspid", pylong_aspid, METH_O},
{"pylong_ispositive", pylong_ispositive, METH_O},
{"pylong_isnegative", pylong_isnegative, METH_O},
{"pylong_iszero", pylong_iszero, METH_O},
{NULL},
};

Expand Down
30 changes: 30 additions & 0 deletions Objects/longobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,36 @@ PyLong_AsUnsignedLongMask(PyObject *op)
return val;
}

int
PyLong_IsPositive(PyObject *obj)
{
if (!PyLong_Check(obj)) {
PyErr_Format(PyExc_TypeError, "expected int, got %T", obj);
return -1;
}
return _PyLong_IsPositive((PyLongObject *)obj);
}

int
PyLong_IsNegative(PyObject *obj)
{
if (!PyLong_Check(obj)) {
PyErr_Format(PyExc_TypeError, "expected int, got %T", obj);
return -1;
}
return _PyLong_IsNegative((PyLongObject *)obj);
}

int
PyLong_IsZero(PyObject *obj)
{
if (!PyLong_Check(obj)) {
PyErr_Format(PyExc_TypeError, "expected int, got %T", obj);
return -1;
}
return _PyLong_IsZero((PyLongObject *)obj);
}

int
_PyLong_Sign(PyObject *vv)
{
Expand Down
Loading