From 008327525488036b103999ed75ec2f7d99ddcfe6 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 13 Jun 2024 16:55:58 +0300 Subject: [PATCH] gh-120446: add PyLong_FlipSign() public function --- Doc/c-api/long.rst | 13 +++++++++++ Doc/whatsnew/3.14.rst | 3 +++ Include/cpython/longobject.h | 14 ++++++++++- Lib/test/test_capi/test_long.py | 22 ++++++++++++++++++ ...-06-13-13-26-42.gh-issue-120446.g_dbYN.rst | 1 + Modules/_testcapi/long.c | 15 +++++++++++- Objects/longobject.c | 23 +++++++++++++++++++ 7 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2024-06-13-13-26-42.gh-issue-120446.g_dbYN.rst diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst index a0e111af5996d7..900769ea87cb63 100644 --- a/Doc/c-api/long.rst +++ b/Doc/c-api/long.rst @@ -507,6 +507,19 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. .. versionadded:: 3.14 +.. c:function:: int PyLong_FlipSign(PyObject **obj) + + Flip the sign of the integer object *\*obj*. + + On success, revert the sign of the integer (negative will be positive and + vice versa) and return 0. + + On failure, return -1 with an exception set. This function always succeeds + if *\*obj* is a :c:type:`PyLongObject` or its subtype. + + .. versionadded:: 3.14 + + .. c:function:: int PyUnstable_Long_IsCompact(const PyLongObject* op) Return 1 if *op* is compact, 0 otherwise. diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index b2dd80b64a691a..c0d10d3eea9edb 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -258,6 +258,9 @@ New Features * Add :c:func:`PyLong_GetSign` function to get the sign of :class:`int` objects. (Contributed by Sergey B Kirpichev in :gh:`116560`.) +* Add :c:func:`PyLong_FlipSign` function to revert the sign of :class:`int` + objects. (Contributed by Sergey B Kirpichev in :gh:`120446`.) + Porting to Python 3.14 ---------------------- diff --git a/Include/cpython/longobject.h b/Include/cpython/longobject.h index 19a6722d07734a..2fbff6a802a6a3 100644 --- a/Include/cpython/longobject.h +++ b/Include/cpython/longobject.h @@ -59,11 +59,23 @@ PyAPI_FUNC(Py_ssize_t) PyUnstable_Long_CompactValue(const PyLongObject* op); 0, -1 or +1 for zero, negative or positive integer, respectively. - On success, set '*sign' to the integer sign, and return 0. - - On failure, set an exception, and return -1. */ + - On failure, set an exception, and return -1. This function always + succeeds if v is a PyLongObject or its subtype. + */ PyAPI_FUNC(int) PyLong_GetSign(PyObject *v, int *sign); PyAPI_FUNC(int) _PyLong_Sign(PyObject *v); +/* PyLong_FlipSign. Flip the sign of the integer object: + turn negative into positive and vice versa. + + On success, revert the sign of the integer and return 0. + + On failure, return -1 with an exception set. This function always succeeds + if *v is a PyLongObject or its subtype. + */ +PyAPI_FUNC(int) PyLong_FlipSign(PyObject **v); + /* _PyLong_NumBits. Return the number of bits needed to represent the absolute value of a long. For example, this returns 1 for 1 and -1, 2 for 2 and -2, and 2 for 3 and -3. It returns 0 for 0. diff --git a/Lib/test/test_capi/test_long.py b/Lib/test/test_capi/test_long.py index 06a29b5a0505b4..fb4c9964c63711 100644 --- a/Lib/test/test_capi/test_long.py +++ b/Lib/test/test_capi/test_long.py @@ -737,6 +737,28 @@ def test_long_getsign(self): # CRASHES getsign(NULL) + def test_long_flipsign(self): + # Test PyLong_FlipSign() + flipsign = _testcapi.pylong_flipsign + getsign = _testcapi.pylong_getsign + a = flipsign(1) + self.assertEqual(getsign(a), -1) + a = flipsign(0) + self.assertEqual(getsign(a), 0) + a = flipsign(-1) + self.assertEqual(getsign(a), 1) + a = flipsign(123456789) + self.assertEqual(getsign(a), -1) + a = flipsign(-123456789) + self.assertEqual(getsign(a), 1) + a = flipsign(IntSubclass(-123456789)) + self.assertEqual(getsign(a), 1) + + self.assertRaises(TypeError, flipsign, 1.0) + self.assertRaises(TypeError, flipsign, Index(123456789)) + + # CRASHES flipsign(NULL) + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/C API/2024-06-13-13-26-42.gh-issue-120446.g_dbYN.rst b/Misc/NEWS.d/next/C API/2024-06-13-13-26-42.gh-issue-120446.g_dbYN.rst new file mode 100644 index 00000000000000..c3d392da628277 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2024-06-13-13-26-42.gh-issue-120446.g_dbYN.rst @@ -0,0 +1 @@ +Add :c:func:`PyLong_FlipSign` function. Patch by Sergey B Kirpichev. diff --git a/Modules/_testcapi/long.c b/Modules/_testcapi/long.c index 2b5e85d5707522..fd45531b4ff953 100644 --- a/Modules/_testcapi/long.c +++ b/Modules/_testcapi/long.c @@ -103,7 +103,19 @@ pylong_getsign(PyObject *module, PyObject *arg) } return PyLong_FromLong(sign); } - +static PyObject * +pylong_flipsign(PyObject *module, PyObject *arg) +{ + NULLABLE(arg); + PyObject *res = arg; + if (PyLong_Check(arg)) { + res = _PyLong_Copy((PyLongObject *)arg); + } + if (PyLong_FlipSign(&res) == -1) { + return NULL; + } + return res; +} static PyObject * pylong_aspid(PyObject *module, PyObject *arg) @@ -123,6 +135,7 @@ static PyMethodDef test_methods[] = { {"pylong_asnativebytes", pylong_asnativebytes, METH_VARARGS}, {"pylong_fromnativebytes", pylong_fromnativebytes, METH_VARARGS}, {"pylong_getsign", pylong_getsign, METH_O}, + {"pylong_flipsign", pylong_flipsign, METH_O}, {"pylong_aspid", pylong_aspid, METH_O}, {NULL}, }; diff --git a/Objects/longobject.c b/Objects/longobject.c index ee0b2a038a2aab..8f7c6a9036b2f5 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -782,6 +782,29 @@ PyLong_GetSign(PyObject *vv, int *sign) return 0; } +int +PyLong_FlipSign(PyObject **vv) +{ + if (!PyLong_Check(*vv)) { + PyErr_Format(PyExc_TypeError, "expect int, got %T", *vv); + return -1; + } + + PyLongObject *x = (PyLongObject *)*vv; + + if (_PyLong_IsCompact(x)) { + stwodigits ival = -medium_value(x); + if (IS_SMALL_INT(ival)) { + Py_DECREF(x); + *vv = get_small_int((sdigit)ival); + return 0; + } + } + + _PyLong_FlipSign(x); + return 0; +} + static int bit_length_digit(digit x) {