diff --git a/Doc/library/math.rst b/Doc/library/math.rst index 43eaba935a1..135adf8f636 100644 --- a/Doc/library/math.rst +++ b/Doc/library/math.rst @@ -213,6 +213,14 @@ Number-theoretic and representation functions of *x* and are floats. +.. function:: nextafter(x, y) + + Return the next floating-point value after *x* towards *y*. + + If *x* is equal to *y*, return *y*. + + .. versionadded:: 3.9 + .. function:: perm(n, k=None) Return the number of ways to choose *k* items from *n* items diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst index 8cfb5725bb5..a686d640ae9 100644 --- a/Doc/whatsnew/3.9.rst +++ b/Doc/whatsnew/3.9.rst @@ -177,6 +177,13 @@ with this change. The overridden methods of :class:`~imaplib.IMAP4_SSL` and :class:`~imaplib.IMAP4_stream` were applied to this change. (Contributed by Dong-hee Na in :issue:`38615`.) +math +---- + +Add :func:`math.nextafter`: return the next floating-point value after *x* +towards *y*. +(Contributed by Victor Stinner in :issue:`39288`.) + nntplib ------- diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py index 5c35c8cff12..b64fd41a548 100644 --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -2033,6 +2033,60 @@ class IsCloseTests(unittest.TestCase): self.assertIs(type(comb(IntSubclass(5), IntSubclass(k))), int) self.assertIs(type(comb(MyIndexable(5), MyIndexable(k))), int) + def assertEqualSign(self, x, y): + """Similar to assertEqual(), but compare also the sign. + + Function useful to check to signed zero. + """ + self.assertEqual(x, y) + self.assertEqual(math.copysign(1.0, x), math.copysign(1.0, y)) + + @requires_IEEE_754 + def test_nextafter(self): + # around 2^52 and 2^63 + self.assertEqual(math.nextafter(4503599627370496.0, -INF), + 4503599627370495.5) + self.assertEqual(math.nextafter(4503599627370496.0, INF), + 4503599627370497.0) + self.assertEqual(math.nextafter(9223372036854775808.0, 0.0), + 9223372036854774784.0) + self.assertEqual(math.nextafter(-9223372036854775808.0, 0.0), + -9223372036854774784.0) + + # around 1.0 + self.assertEqual(math.nextafter(1.0, -INF), + float.fromhex('0x1.fffffffffffffp-1')) + self.assertEqual(math.nextafter(1.0, INF), + float.fromhex('0x1.0000000000001p+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) + + # around 0.0 + smallest_subnormal = sys.float_info.min * sys.float_info.epsilon + self.assertEqual(math.nextafter(+0.0, INF), smallest_subnormal) + self.assertEqual(math.nextafter(-0.0, INF), smallest_subnormal) + self.assertEqual(math.nextafter(+0.0, -INF), -smallest_subnormal) + self.assertEqual(math.nextafter(-0.0, -INF), -smallest_subnormal) + self.assertEqualSign(math.nextafter(smallest_subnormal, +0.0), +0.0) + self.assertEqualSign(math.nextafter(-smallest_subnormal, +0.0), -0.0) + self.assertEqualSign(math.nextafter(smallest_subnormal, -0.0), +0.0) + self.assertEqualSign(math.nextafter(-smallest_subnormal, -0.0), -0.0) + + # around infinity + largest_normal = sys.float_info.max + self.assertEqual(math.nextafter(INF, 0.0), largest_normal) + self.assertEqual(math.nextafter(-INF, 0.0), -largest_normal) + self.assertEqual(math.nextafter(largest_normal, INF), INF) + self.assertEqual(math.nextafter(-largest_normal, -INF), -INF) + + # NaN + self.assertTrue(math.isnan(math.nextafter(NAN, 1.0))) + self.assertTrue(math.isnan(math.nextafter(1.0, NAN))) + self.assertTrue(math.isnan(math.nextafter(NAN, NAN))) + def test_main(): from doctest import DocFileSuite diff --git a/Misc/NEWS.d/next/Library/2020-01-10-16-52-09.bpo-39288.IB-aQX.rst b/Misc/NEWS.d/next/Library/2020-01-10-16-52-09.bpo-39288.IB-aQX.rst new file mode 100644 index 00000000000..0e0ec99c344 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-01-10-16-52-09.bpo-39288.IB-aQX.rst @@ -0,0 +1,2 @@ +Add :func:`math.nextafter`: return the next floating-point value after *x* +towards *y*. diff --git a/Modules/clinic/mathmodule.c.h b/Modules/clinic/mathmodule.c.h index 95d68ee55ae..f34633cb0c0 100644 --- a/Modules/clinic/mathmodule.c.h +++ b/Modules/clinic/mathmodule.c.h @@ -808,4 +808,52 @@ math_comb(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=9a2b3dc91eb9aadd input=a9049054013a1b77]*/ + +PyDoc_STRVAR(math_nextafter__doc__, +"nextafter($module, x, y, /)\n" +"--\n" +"\n" +"Return the next floating-point value after x towards y."); + +#define MATH_NEXTAFTER_METHODDEF \ + {"nextafter", (PyCFunction)(void(*)(void))math_nextafter, METH_FASTCALL, math_nextafter__doc__}, + +static PyObject * +math_nextafter_impl(PyObject *module, double x, double y); + +static PyObject * +math_nextafter(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + double x; + double y; + + if (!_PyArg_CheckPositional("nextafter", nargs, 2, 2)) { + goto exit; + } + if (PyFloat_CheckExact(args[0])) { + x = PyFloat_AS_DOUBLE(args[0]); + } + else + { + x = PyFloat_AsDouble(args[0]); + if (x == -1.0 && PyErr_Occurred()) { + goto exit; + } + } + if (PyFloat_CheckExact(args[1])) { + y = PyFloat_AS_DOUBLE(args[1]); + } + else + { + y = PyFloat_AsDouble(args[1]); + if (y == -1.0 && PyErr_Occurred()) { + goto exit; + } + } + return_value = math_nextafter_impl(module, x, y); + +exit: + return return_value; +} +/*[clinic end generated code: output=e4ed1a800e4b2eae input=a9049054013a1b77]*/ diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index e60e19bc490..632a421e3bb 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -3295,6 +3295,25 @@ error: } +/*[clinic input] +math.nextafter + + x: double + y: double + / + +Return the next floating-point value 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]*/ +{ + double f = nextafter(x, y); + return PyFloat_FromDouble(f); +} + + static PyMethodDef math_methods[] = { {"acos", math_acos, METH_O, math_acos_doc}, {"acosh", math_acosh, METH_O, math_acosh_doc}, @@ -3346,6 +3365,7 @@ static PyMethodDef math_methods[] = { MATH_PROD_METHODDEF MATH_PERM_METHODDEF MATH_COMB_METHODDEF + MATH_NEXTAFTER_METHODDEF {NULL, NULL} /* sentinel */ };