diff --git a/Doc/library/math.rst b/Doc/library/math.rst index 6d9a992f731316..1a1bd63a202390 100644 --- a/Doc/library/math.rst +++ b/Doc/library/math.rst @@ -229,9 +229,9 @@ Number-theoretic and representation functions of *x* and are floats. -.. function:: nextafter(x, y) +.. function:: nextafter(x, y, /, *, steps=1) - Return the next floating-point value after *x* towards *y*. + Return the floating-point value *steps* steps after *x* towards *y*. If *x* is equal to *y*, return *y*. @@ -244,6 +244,9 @@ Number-theoretic and representation functions See also :func:`math.ulp`. + .. versionchanged:: 3.12 + Added the *steps* argument. + .. versionadded:: 3.9 .. function:: perm(n, k=None) diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py index cfaf3b3ea26a7e..30f5bb37a300ad 100644 --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -2036,11 +2036,20 @@ def test_nextafter(self): float.fromhex('0x1.fffffffffffffp-1')) self.assertEqual(math.nextafter(1.0, INF), float.fromhex('0x1.0000000000001p+0')) + self.assertEqual(math.nextafter(1.0, -INF, steps=1), + float.fromhex('0x1.fffffffffffffp-1')) + self.assertEqual(math.nextafter(1.0, INF, steps=1), + float.fromhex('0x1.0000000000001p+0')) + self.assertEqual(math.nextafter(1.0, -INF, steps=3), + float.fromhex('0x1.ffffffffffffdp-1')) + self.assertEqual(math.nextafter(1.0, INF, steps=3), + float.fromhex('0x1.0000000000003p+0')) # x == y: y is returned - self.assertEqual(math.nextafter(2.0, 2.0), 2.0) - self.assertEqualSign(math.nextafter(-0.0, +0.0), +0.0) - self.assertEqualSign(math.nextafter(+0.0, -0.0), -0.0) + for steps in range(1, 5): + self.assertEqual(math.nextafter(2.0, 2.0, steps=steps), 2.0) + self.assertEqualSign(math.nextafter(-0.0, +0.0, steps=steps), +0.0) + self.assertEqualSign(math.nextafter(+0.0, -0.0, steps=steps), -0.0) # around 0.0 smallest_subnormal = sys.float_info.min * sys.float_info.epsilon @@ -2065,6 +2074,12 @@ def test_nextafter(self): self.assertIsNaN(math.nextafter(1.0, NAN)) self.assertIsNaN(math.nextafter(NAN, NAN)) + with self.assertRaises(ValueError): + math.nextafter(1.0, INF, steps=0) + with self.assertRaises(ValueError): + math.nextafter(1.0, INF, steps=-1) + + @requires_IEEE_754 def test_ulp(self): self.assertEqual(math.ulp(1.0), sys.float_info.epsilon) diff --git a/Misc/NEWS.d/next/Library/2022-07-16-17-15-29.gh-issue-94906.C4G8DG.rst b/Misc/NEWS.d/next/Library/2022-07-16-17-15-29.gh-issue-94906.C4G8DG.rst new file mode 100644 index 00000000000000..0c01172d1f5d06 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-07-16-17-15-29.gh-issue-94906.C4G8DG.rst @@ -0,0 +1 @@ +Support multiple steps in :func:`math.nextafter`. Patch by Shantanu Jain. diff --git a/Modules/clinic/mathmodule.c.h b/Modules/clinic/mathmodule.c.h index efabbf9714237c..32f86588c63173 100644 --- a/Modules/clinic/mathmodule.c.h +++ b/Modules/clinic/mathmodule.c.h @@ -780,25 +780,31 @@ math_comb(PyObject *module, PyObject *const *args, Py_ssize_t nargs) } PyDoc_STRVAR(math_nextafter__doc__, -"nextafter($module, x, y, /)\n" +"nextafter($module, x, y, /, *, steps=1)\n" "--\n" "\n" -"Return the next floating-point value after x towards y."); +"Return the floating-point value the given number of steps after x towards y."); #define MATH_NEXTAFTER_METHODDEF \ - {"nextafter", _PyCFunction_CAST(math_nextafter), METH_FASTCALL, math_nextafter__doc__}, + {"nextafter", _PyCFunction_CAST(math_nextafter), METH_FASTCALL|METH_KEYWORDS, math_nextafter__doc__}, static PyObject * -math_nextafter_impl(PyObject *module, double x, double y); +math_nextafter_impl(PyObject *module, double x, double y, int steps); static PyObject * -math_nextafter(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +math_nextafter(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + static const char * const _keywords[] = {"", "", "steps", NULL}; + static _PyArg_Parser _parser = {NULL, _keywords, "nextafter", 0}; + PyObject *argsbuf[3]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 2; double x; double y; + int steps = 1; - if (!_PyArg_CheckPositional("nextafter", nargs, 2, 2)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 0, argsbuf); + if (!args) { goto exit; } if (PyFloat_CheckExact(args[0])) { @@ -821,7 +827,15 @@ math_nextafter(PyObject *module, PyObject *const *args, Py_ssize_t nargs) goto exit; } } - return_value = math_nextafter_impl(module, x, y); + if (!noptargs) { + goto skip_optional_kwonly; + } + steps = _PyLong_AsInt(args[2]); + if (steps == -1 && PyErr_Occurred()) { + goto exit; + } +skip_optional_kwonly: + return_value = math_nextafter_impl(module, x, y, steps); exit: return return_value; @@ -865,4 +879,4 @@ math_ulp(PyObject *module, PyObject *arg) exit: return return_value; } -/*[clinic end generated code: output=965f99dabaa72165 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=72bf69446859d847 input=a9049054013a1b77]*/ diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 48625c8c18d70e..d07ab7eb52c5d8 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -3781,14 +3781,17 @@ math.nextafter x: double y: double / + * + steps: int = 1 -Return the next floating-point value after x towards y. +Return the floating-point value the given number of steps after x towards y. [clinic start generated code]*/ static PyObject * -math_nextafter_impl(PyObject *module, double x, double y) -/*[clinic end generated code: output=750c8266c1c540ce input=02b2d50cd1d9f9b6]*/ +math_nextafter_impl(PyObject *module, double x, double y, int steps) +/*[clinic end generated code: output=14190eb869199e5a input=a794e7a79768ee25]*/ { + int i; #if defined(_AIX) if (x == y) { /* On AIX 7.1, libm nextafter(-0.0, +0.0) returns -0.0. @@ -3802,7 +3805,14 @@ math_nextafter_impl(PyObject *module, double x, double y) return PyFloat_FromDouble(y); } #endif - return PyFloat_FromDouble(nextafter(x, y)); + if (steps < 1) { + PyErr_SetString(PyExc_ValueError, "steps must be >= 1"); + return NULL; + } + for (i = 0; i < steps; i++) { + x = nextafter(x, y); + } + return PyFloat_FromDouble(x); }