From 77fb4ae122bd9b22d581f0b849490310f7542771 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Wed, 26 Apr 2023 22:38:42 +0800 Subject: [PATCH 01/19] pythongh-94906: Support multiple steps in math.nextafter --- Doc/library/math.rst | 7 +- .../pycore_global_objects_fini_generated.h | 1 + Include/internal/pycore_global_strings.h | 1 + .../internal/pycore_runtime_init_generated.h | 1 + .../internal/pycore_unicodeobject_generated.h | 3 + Lib/test/test_math.py | 20 +++++- ...2-07-16-17-15-29.gh-issue-94906.C4G8DG.rst | 1 + Modules/clinic/mathmodule.c.h | 53 ++++++++++++--- Modules/mathmodule.c | 68 +++++++++++++++++-- 9 files changed, 138 insertions(+), 17 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-07-16-17-15-29.gh-issue-94906.C4G8DG.rst diff --git a/Doc/library/math.rst b/Doc/library/math.rst index 797f32408eac3d..edc6a7910f8ea4 100644 --- a/Doc/library/math.rst +++ b/Doc/library/math.rst @@ -224,9 +224,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*. @@ -239,6 +239,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/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index fdfa80bd7d424a..bac49ae07bed55 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -1169,6 +1169,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(stdin)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(stdout)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(step)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(steps)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(store_name)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(strategy)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(strftime)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 6f430bb25eb8d3..e396e69ec3ef9f 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -657,6 +657,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(stdin) STRUCT_FOR_ID(stdout) STRUCT_FOR_ID(step) + STRUCT_FOR_ID(steps) STRUCT_FOR_ID(store_name) STRUCT_FOR_ID(strategy) STRUCT_FOR_ID(strftime) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 0452c4c61551de..af8e9eb55e59e2 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -1163,6 +1163,7 @@ extern "C" { INIT_ID(stdin), \ INIT_ID(stdout), \ INIT_ID(step), \ + INIT_ID(steps), \ INIT_ID(store_name), \ INIT_ID(strategy), \ INIT_ID(strftime), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 7114a5416f2515..a1a20323045723 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -1824,6 +1824,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(step); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(steps); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(store_name); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py index 433161c2dd4145..2be685b2690646 100644 --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -2296,11 +2296,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 @@ -2325,6 +2334,11 @@ def test_nextafter(self): self.assertIsNaN(math.nextafter(1.0, NAN)) self.assertIsNaN(math.nextafter(NAN, NAN)) + self.assertEqual(1.0, 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..663343371d1b99 --- /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 and Matthias Gorgens. diff --git a/Modules/clinic/mathmodule.c.h b/Modules/clinic/mathmodule.c.h index bc5bbceb4c92b6..81f0ab725c86f0 100644 --- a/Modules/clinic/mathmodule.c.h +++ b/Modules/clinic/mathmodule.c.h @@ -826,25 +826,54 @@ 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; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(steps), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"", "", "steps", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "nextafter", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + 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])) { @@ -867,7 +896,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; @@ -911,4 +948,4 @@ math_ulp(PyObject *module, PyObject *arg) exit: return return_value; } -/*[clinic end generated code: output=a6437a3ba18c486a input=a9049054013a1b77]*/ +/*[clinic end generated code: output=b3ff18abb82b4f3c input=a9049054013a1b77]*/ diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index eddc1a33a953e6..1295b63ad2c41d 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -3893,13 +3893,15 @@ 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]*/ { #if defined(_AIX) if (x == y) { @@ -3914,7 +3916,65 @@ math_nextafter_impl(PyObject *module, double x, double y) return PyFloat_FromDouble(y); } #endif - return PyFloat_FromDouble(nextafter(x, y)); + // fast path: + if (steps == 1) { + return PyFloat_FromDouble(nextafter(x, y)); + } + if (steps < 0) { + PyErr_SetString(PyExc_ValueError, "steps must be >= 0"); + return NULL; + } + if (steps == 0) + return PyFloat_FromDouble(x); + if (Py_IS_NAN(x) || Py_IS_NAN(y)) + return PyFloat_FromDouble(x+y); + + uint64_t usteps = steps; + + union pun {double f; uint64_t i;}; + union pun ux = {x}, uy = {y}; + if(ux.i == uy.i) { + return PyFloat_FromDouble(x); + } + + const uint64_t sign_bit = 1ULL<<63; + + uint64_t ax = ux.i & ~sign_bit; + uint64_t ay = uy.i & ~sign_bit; + + // opposite signs + if (((ux.i ^ uy.i) & sign_bit)) { + if (ax + ay <= usteps) { + return PyFloat_FromDouble(uy.f); + // This comparison has to use <, because <= would get +0.0 vs -0.0 + // wrong. + } else if (ax < usteps) { + union pun result = {.i = (uy.i & sign_bit) | (usteps - ax)}; + return PyFloat_FromDouble(result.f); + } else { + ux.i -= usteps; + return PyFloat_FromDouble(ux.f); + } + // same sign + } else if (ax > ay) { + // the addition is not UB, + // because we have an extra bit at the top of ax and usteps. + if (ax >= ay + usteps) { + ux.i -= usteps; + return PyFloat_FromDouble(ux.f); + } else { + return PyFloat_FromDouble(uy.f); + } + } else { + // the addition is not UB, + // because we have an extra bit at the top of ax and usteps. + if (ax + usteps <= ay) { + ux.i += usteps; + return PyFloat_FromDouble(ux.f); + } else { + return PyFloat_FromDouble(uy.f); + } + } } From b90e86ce204282b5e48feb6e59b85cb701dd4297 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Wed, 17 May 2023 14:30:26 +0800 Subject: [PATCH 02/19] Pass through NaN unchanged --- Modules/mathmodule.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 7918c98aba97cc..42a251d9f817b9 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -3897,8 +3897,10 @@ math_nextafter_impl(PyObject *module, double x, double y, int steps) } if (steps == 0) return PyFloat_FromDouble(x); - if (Py_IS_NAN(x) || Py_IS_NAN(y)) - return PyFloat_FromDouble(x+y); + if (Py_IS_NAN(x)) + return PyFloat_FromDouble(x); + if (Py_IS_NAN(y)) + return PyFloat_FromDouble(y); uint64_t usteps = steps; From accc22faea59029875723f95e37cbe98c73c9522 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Wed, 17 May 2023 14:33:21 +0800 Subject: [PATCH 03/19] Explain that we need same endianness --- Modules/mathmodule.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 42a251d9f817b9..288828ca933338 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -3904,6 +3904,10 @@ math_nextafter_impl(PyObject *module, double x, double y, int steps) uint64_t usteps = steps; + // We assume that double and uint64_t have the same endianness. + // This is not guaranteed by the C-standard, but it is true for + // all platforms we care about. (The most likely form of violation + // would be a "mixed-endian" double.) union pun {double f; uint64_t i;}; union pun ux = {x}, uy = {y}; if(ux.i == uy.i) { From 8e1db3935c9c980882c3249b3753bde9f5efb892 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Wed, 17 May 2023 15:33:59 +0800 Subject: [PATCH 04/19] Allow arbitrarily many steps --- Modules/mathmodule.c | 59 +++++++++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 288828ca933338..203673dab33e61 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -3865,15 +3865,20 @@ math.nextafter y: double / * - steps: int = 1 + steps: object = none Return the floating-point value the given number of steps after x towards y. +If steps is not specified or is None, it defaults to 1. + +Raises a TypeError, if x or y is not a double, or if steps is not an integer. +Raises ValueError if steps is negative. [clinic start generated code]*/ static PyObject * -math_nextafter_impl(PyObject *module, double x, double y, int steps) +math_nextafter_impl(PyObject *module, double x, double y, PyObject* steps) /*[clinic end generated code: output=14190eb869199e5a input=a794e7a79768ee25]*/ { + // TODO(Matthias): can we use steps == NULL for default? #if defined(_AIX) if (x == y) { /* On AIX 7.1, libm nextafter(-0.0, +0.0) returns -0.0. @@ -3887,22 +3892,44 @@ math_nextafter_impl(PyObject *module, double x, double y, int steps) return PyFloat_FromDouble(y); } #endif - // fast path: - if (steps == 1) { + if (steps == NULL) { + // fast path: we default to one step. return PyFloat_FromDouble(nextafter(x, y)); } - if (steps < 0) { - PyErr_SetString(PyExc_ValueError, "steps must be >= 0"); + steps = PyNumber_Index(steps); + if (steps == NULL) { return NULL; } - if (steps == 0) + assert(PyLong_CheckExact(steps)); + assert(overflow >= 0 && !PyErr_Occurred()); + if (_PyLong_IsNegative((PyLongObject *)steps)) { + PyErr_SetString(PyExc_ValueError, + "steps must be a non-negative integer"); + goto error; + } + + uint64_t usteps = PyLong_AsUnsignedLongLong(steps); + // Conveniently, uint64_t and double have the same number of bits + // on all the platforms we care about. + // So if an overflow occurs, we can just use UINT64_MAX. + if (usteps == (unsigned long long)-1 && PyErr_Occurred()) { + if(!PyErr_ExceptionMatches(PyExc_OverflowError)) { + Py_DECREF(steps); + return NULL; + } + PyErr_Clear(); + usteps = UINT64_MAX; + } + Py_DECREF(steps); + if (usteps == 0) { return PyFloat_FromDouble(x); - if (Py_IS_NAN(x)) + } + if (Py_IS_NAN(x)) { return PyFloat_FromDouble(x); - if (Py_IS_NAN(y)) + } + if (Py_IS_NAN(y)) { return PyFloat_FromDouble(y); - - uint64_t usteps = steps; + } // We assume that double and uint64_t have the same endianness. // This is not guaranteed by the C-standard, but it is true for @@ -3921,6 +3948,8 @@ math_nextafter_impl(PyObject *module, double x, double y, int steps) // opposite signs if (((ux.i ^ uy.i) & sign_bit)) { + // NOTE: ax + ay can never overflow, because there most significant bit + // ain't set. if (ax + ay <= usteps) { return PyFloat_FromDouble(uy.f); // This comparison has to use <, because <= would get +0.0 vs -0.0 @@ -3934,18 +3963,14 @@ math_nextafter_impl(PyObject *module, double x, double y, int steps) } // same sign } else if (ax > ay) { - // the addition is not UB, - // because we have an extra bit at the top of ax and usteps. - if (ax >= ay + usteps) { + if (ax - ay >= usteps) { ux.i -= usteps; return PyFloat_FromDouble(ux.f); } else { return PyFloat_FromDouble(uy.f); } } else { - // the addition is not UB, - // because we have an extra bit at the top of ax and usteps. - if (ax + usteps <= ay) { + if (ay - ax >= usteps) { ux.i += usteps; return PyFloat_FromDouble(ux.f); } else { From 687334756eb48f245033b9e90e183c6a01e9379e Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Wed, 17 May 2023 15:43:56 +0800 Subject: [PATCH 05/19] Fix problems --- Modules/clinic/mathmodule.c.h | 9 +++------ Modules/mathmodule.c | 5 ++--- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/Modules/clinic/mathmodule.c.h b/Modules/clinic/mathmodule.c.h index 81f0ab725c86f0..84131003056917 100644 --- a/Modules/clinic/mathmodule.c.h +++ b/Modules/clinic/mathmodule.c.h @@ -835,7 +835,7 @@ PyDoc_STRVAR(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, int steps); +math_nextafter_impl(PyObject *module, double x, double y, PyObject* steps); static PyObject * math_nextafter(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) @@ -870,7 +870,7 @@ math_nextafter(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObje Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 2; double x; double y; - int steps = 1; + PyObject* steps = NULL; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 0, argsbuf); if (!args) { @@ -899,10 +899,7 @@ math_nextafter(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObje if (!noptargs) { goto skip_optional_kwonly; } - steps = _PyLong_AsInt(args[2]); - if (steps == -1 && PyErr_Occurred()) { - goto exit; - } + steps = args[2]; skip_optional_kwonly: return_value = math_nextafter_impl(module, x, y, steps); diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 203673dab33e61..adb9acb70913bf 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -3878,7 +3878,6 @@ static PyObject * math_nextafter_impl(PyObject *module, double x, double y, PyObject* steps) /*[clinic end generated code: output=14190eb869199e5a input=a794e7a79768ee25]*/ { - // TODO(Matthias): can we use steps == NULL for default? #if defined(_AIX) if (x == y) { /* On AIX 7.1, libm nextafter(-0.0, +0.0) returns -0.0. @@ -3901,11 +3900,11 @@ math_nextafter_impl(PyObject *module, double x, double y, PyObject* steps) return NULL; } assert(PyLong_CheckExact(steps)); - assert(overflow >= 0 && !PyErr_Occurred()); if (_PyLong_IsNegative((PyLongObject *)steps)) { PyErr_SetString(PyExc_ValueError, "steps must be a non-negative integer"); - goto error; + Py_DECREF(steps); + return NULL; } uint64_t usteps = PyLong_AsUnsignedLongLong(steps); From 1fff8b4ffd6e82a5a83b65c862ee52a9b31e9bc4 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Wed, 17 May 2023 15:53:43 +0800 Subject: [PATCH 06/19] Update regen-all --- Modules/clinic/mathmodule.c.h | 15 ++++++++++----- Modules/mathmodule.c | 7 ++++--- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Modules/clinic/mathmodule.c.h b/Modules/clinic/mathmodule.c.h index 84131003056917..c16c1b083985f2 100644 --- a/Modules/clinic/mathmodule.c.h +++ b/Modules/clinic/mathmodule.c.h @@ -826,16 +826,21 @@ math_comb(PyObject *module, PyObject *const *args, Py_ssize_t nargs) } PyDoc_STRVAR(math_nextafter__doc__, -"nextafter($module, x, y, /, *, steps=1)\n" +"nextafter($module, x, y, /, *, steps=None)\n" "--\n" "\n" -"Return the floating-point value the given number of steps after x towards y."); +"Return the floating-point value the given number of steps after x towards y.\n" +"\n" +"If steps is not specified or is None, it defaults to 1.\n" +"\n" +"Raises a TypeError, if x or y is not a double, or if steps is not an integer.\n" +"Raises ValueError if steps is negative."); #define MATH_NEXTAFTER_METHODDEF \ {"nextafter", _PyCFunction_CAST(math_nextafter), METH_FASTCALL|METH_KEYWORDS, math_nextafter__doc__}, static PyObject * -math_nextafter_impl(PyObject *module, double x, double y, PyObject* steps); +math_nextafter_impl(PyObject *module, double x, double y, PyObject *steps); static PyObject * math_nextafter(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) @@ -870,7 +875,7 @@ math_nextafter(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObje Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 2; double x; double y; - PyObject* steps = NULL; + PyObject *steps = Py_None; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 0, argsbuf); if (!args) { @@ -945,4 +950,4 @@ math_ulp(PyObject *module, PyObject *arg) exit: return return_value; } -/*[clinic end generated code: output=b3ff18abb82b4f3c input=a9049054013a1b77]*/ +/*[clinic end generated code: output=91a0357265a2a553 input=a9049054013a1b77]*/ diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index adb9acb70913bf..1c33a2a88413fb 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -3865,9 +3865,10 @@ math.nextafter y: double / * - steps: object = none + steps: object = None Return the floating-point value the given number of steps after x towards y. + If steps is not specified or is None, it defaults to 1. Raises a TypeError, if x or y is not a double, or if steps is not an integer. @@ -3875,8 +3876,8 @@ Raises ValueError if steps is negative. [clinic start generated code]*/ static PyObject * -math_nextafter_impl(PyObject *module, double x, double y, PyObject* steps) -/*[clinic end generated code: output=14190eb869199e5a input=a794e7a79768ee25]*/ +math_nextafter_impl(PyObject *module, double x, double y, PyObject *steps) +/*[clinic end generated code: output=cc6511f02afc099e input=7f2a5842112af2b4]*/ { #if defined(_AIX) if (x == y) { From 311f266a13082684e5552ed2a33f192fb747aaf6 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Wed, 17 May 2023 15:58:39 +0800 Subject: [PATCH 07/19] Make steps also available as a non-kw arg --- .../pycore_global_objects_fini_generated.h | 1 - Include/internal/pycore_global_strings.h | 1 - .../internal/pycore_runtime_init_generated.h | 1 - .../internal/pycore_unicodeobject_generated.h | 3 -- Modules/clinic/mathmodule.c.h | 44 ++++--------------- Modules/mathmodule.c | 5 +-- 6 files changed, 10 insertions(+), 45 deletions(-) diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index d96f2eb354b509..24a268ac8c43ec 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -1192,7 +1192,6 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(stdin)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(stdout)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(step)); - _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(steps)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(store_name)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(strategy)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(strftime)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 1d094b25d401fe..c1005d05155271 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -680,7 +680,6 @@ struct _Py_global_strings { STRUCT_FOR_ID(stdin) STRUCT_FOR_ID(stdout) STRUCT_FOR_ID(step) - STRUCT_FOR_ID(steps) STRUCT_FOR_ID(store_name) STRUCT_FOR_ID(strategy) STRUCT_FOR_ID(strftime) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 4cd0137ce3c609..ff1dee6eacfe5d 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -1186,7 +1186,6 @@ extern "C" { INIT_ID(stdin), \ INIT_ID(stdout), \ INIT_ID(step), \ - INIT_ID(steps), \ INIT_ID(store_name), \ INIT_ID(strategy), \ INIT_ID(strftime), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 1f4f885a524c31..ba6b37f1bf55b3 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -1881,9 +1881,6 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(step); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); - string = &_Py_ID(steps); - assert(_PyUnicode_CheckConsistency(string, 1)); - _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(store_name); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); diff --git a/Modules/clinic/mathmodule.c.h b/Modules/clinic/mathmodule.c.h index c16c1b083985f2..adcfb08b4fbfbc 100644 --- a/Modules/clinic/mathmodule.c.h +++ b/Modules/clinic/mathmodule.c.h @@ -826,7 +826,7 @@ math_comb(PyObject *module, PyObject *const *args, Py_ssize_t nargs) } PyDoc_STRVAR(math_nextafter__doc__, -"nextafter($module, x, y, /, *, steps=None)\n" +"nextafter($module, x, y, steps=None, /)\n" "--\n" "\n" "Return the floating-point value the given number of steps after x towards y.\n" @@ -837,48 +837,20 @@ PyDoc_STRVAR(math_nextafter__doc__, "Raises ValueError if steps is negative."); #define MATH_NEXTAFTER_METHODDEF \ - {"nextafter", _PyCFunction_CAST(math_nextafter), METH_FASTCALL|METH_KEYWORDS, math_nextafter__doc__}, + {"nextafter", _PyCFunction_CAST(math_nextafter), METH_FASTCALL, math_nextafter__doc__}, static PyObject * math_nextafter_impl(PyObject *module, double x, double y, PyObject *steps); static PyObject * -math_nextafter(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +math_nextafter(PyObject *module, PyObject *const *args, Py_ssize_t nargs) { PyObject *return_value = NULL; - #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - - #define NUM_KEYWORDS 1 - static struct { - PyGC_Head _this_is_not_used; - PyObject_VAR_HEAD - PyObject *ob_item[NUM_KEYWORDS]; - } _kwtuple = { - .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) - .ob_item = { &_Py_ID(steps), }, - }; - #undef NUM_KEYWORDS - #define KWTUPLE (&_kwtuple.ob_base.ob_base) - - #else // !Py_BUILD_CORE - # define KWTUPLE NULL - #endif // !Py_BUILD_CORE - - static const char * const _keywords[] = {"", "", "steps", NULL}; - static _PyArg_Parser _parser = { - .keywords = _keywords, - .fname = "nextafter", - .kwtuple = KWTUPLE, - }; - #undef KWTUPLE - PyObject *argsbuf[3]; - Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 2; double x; double y; PyObject *steps = Py_None; - args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 0, argsbuf); - if (!args) { + if (!_PyArg_CheckPositional("nextafter", nargs, 2, 3)) { goto exit; } if (PyFloat_CheckExact(args[0])) { @@ -901,11 +873,11 @@ math_nextafter(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObje goto exit; } } - if (!noptargs) { - goto skip_optional_kwonly; + if (nargs < 3) { + goto skip_optional; } steps = args[2]; -skip_optional_kwonly: +skip_optional: return_value = math_nextafter_impl(module, x, y, steps); exit: @@ -950,4 +922,4 @@ math_ulp(PyObject *module, PyObject *arg) exit: return return_value; } -/*[clinic end generated code: output=91a0357265a2a553 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=029a00d4b17ac37c input=a9049054013a1b77]*/ diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 1c33a2a88413fb..2ef9dd24521fb7 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -3863,9 +3863,8 @@ math.nextafter x: double y: double - / - * steps: object = None + / Return the floating-point value the given number of steps after x towards y. @@ -3877,7 +3876,7 @@ Raises ValueError if steps is negative. static PyObject * math_nextafter_impl(PyObject *module, double x, double y, PyObject *steps) -/*[clinic end generated code: output=cc6511f02afc099e input=7f2a5842112af2b4]*/ +/*[clinic end generated code: output=cc6511f02afc099e input=fa9d9e3472b55cc1]*/ { #if defined(_AIX) if (x == y) { From cd70fead1d495ef775ad92a4a6f8e4f473854e37 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Wed, 17 May 2023 16:09:49 +0800 Subject: [PATCH 08/19] Fix docs --- Doc/library/math.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/math.rst b/Doc/library/math.rst index edc6a7910f8ea4..49cfe605f47d38 100644 --- a/Doc/library/math.rst +++ b/Doc/library/math.rst @@ -228,7 +228,7 @@ Number-theoretic and representation functions Return the floating-point value *steps* steps after *x* towards *y*. - If *x* is equal to *y*, return *y*. + If *x* is equal to *y*, return *y*, unless *steps* is zero. Examples: From e0421b609710f662302d04a5dcdd5db24ed17cab Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Wed, 17 May 2023 16:19:24 +0800 Subject: [PATCH 09/19] Update docs --- Doc/library/math.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/math.rst b/Doc/library/math.rst index 49cfe605f47d38..9e58b552576ce6 100644 --- a/Doc/library/math.rst +++ b/Doc/library/math.rst @@ -224,7 +224,7 @@ Number-theoretic and representation functions of *x* and are floats. -.. function:: nextafter(x, y, /, *, steps=1) +.. function:: nextafter(x, y, steps=1) Return the floating-point value *steps* steps after *x* towards *y*. From a8c83a7f129cef5398b55194ad26abff1d070b1b Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Wed, 17 May 2023 16:22:44 +0800 Subject: [PATCH 10/19] Py_None instead of NULL --- Modules/mathmodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 2ef9dd24521fb7..36c0374abf9de0 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -3891,7 +3891,7 @@ math_nextafter_impl(PyObject *module, double x, double y, PyObject *steps) return PyFloat_FromDouble(y); } #endif - if (steps == NULL) { + if (steps == Py_None) { // fast path: we default to one step. return PyFloat_FromDouble(nextafter(x, y)); } From 88c46c4807a68d8727e25c2cd7c6eeb77e7108c2 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Wed, 17 May 2023 16:25:07 +0800 Subject: [PATCH 11/19] Fix typo --- Modules/mathmodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 36c0374abf9de0..eacb2f2ecbca2f 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -3947,7 +3947,7 @@ math_nextafter_impl(PyObject *module, double x, double y, PyObject *steps) // opposite signs if (((ux.i ^ uy.i) & sign_bit)) { - // NOTE: ax + ay can never overflow, because there most significant bit + // NOTE: ax + ay can never overflow, because their most significant bit // ain't set. if (ax + ay <= usteps) { return PyFloat_FromDouble(uy.f); From 2232bb40a68ef9c53bde2995fb8d4a56b54603cf Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Wed, 17 May 2023 16:35:04 +0800 Subject: [PATCH 12/19] Tickle the CI/CD From a2724f69a57de10a553bec875e1d16215fe175e6 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Wed, 17 May 2023 16:38:39 +0800 Subject: [PATCH 13/19] Revert "Make steps also available as a non-kw arg" This reverts commit 311f266a13082684e5552ed2a33f192fb747aaf6. --- .../pycore_global_objects_fini_generated.h | 1 + Include/internal/pycore_global_strings.h | 1 + .../internal/pycore_runtime_init_generated.h | 1 + .../internal/pycore_unicodeobject_generated.h | 3 ++ Modules/clinic/mathmodule.c.h | 44 +++++++++++++++---- Modules/mathmodule.c | 5 ++- 6 files changed, 45 insertions(+), 10 deletions(-) diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 24a268ac8c43ec..d96f2eb354b509 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -1192,6 +1192,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(stdin)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(stdout)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(step)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(steps)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(store_name)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(strategy)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(strftime)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index c1005d05155271..1d094b25d401fe 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -680,6 +680,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(stdin) STRUCT_FOR_ID(stdout) STRUCT_FOR_ID(step) + STRUCT_FOR_ID(steps) STRUCT_FOR_ID(store_name) STRUCT_FOR_ID(strategy) STRUCT_FOR_ID(strftime) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index ff1dee6eacfe5d..4cd0137ce3c609 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -1186,6 +1186,7 @@ extern "C" { INIT_ID(stdin), \ INIT_ID(stdout), \ INIT_ID(step), \ + INIT_ID(steps), \ INIT_ID(store_name), \ INIT_ID(strategy), \ INIT_ID(strftime), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index ba6b37f1bf55b3..1f4f885a524c31 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -1881,6 +1881,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(step); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(steps); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(store_name); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); diff --git a/Modules/clinic/mathmodule.c.h b/Modules/clinic/mathmodule.c.h index adcfb08b4fbfbc..c16c1b083985f2 100644 --- a/Modules/clinic/mathmodule.c.h +++ b/Modules/clinic/mathmodule.c.h @@ -826,7 +826,7 @@ math_comb(PyObject *module, PyObject *const *args, Py_ssize_t nargs) } PyDoc_STRVAR(math_nextafter__doc__, -"nextafter($module, x, y, steps=None, /)\n" +"nextafter($module, x, y, /, *, steps=None)\n" "--\n" "\n" "Return the floating-point value the given number of steps after x towards y.\n" @@ -837,20 +837,48 @@ PyDoc_STRVAR(math_nextafter__doc__, "Raises ValueError if steps is negative."); #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, PyObject *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; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(steps), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"", "", "steps", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "nextafter", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[3]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 2; double x; double y; PyObject *steps = Py_None; - if (!_PyArg_CheckPositional("nextafter", nargs, 2, 3)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 0, argsbuf); + if (!args) { goto exit; } if (PyFloat_CheckExact(args[0])) { @@ -873,11 +901,11 @@ math_nextafter(PyObject *module, PyObject *const *args, Py_ssize_t nargs) goto exit; } } - if (nargs < 3) { - goto skip_optional; + if (!noptargs) { + goto skip_optional_kwonly; } steps = args[2]; -skip_optional: +skip_optional_kwonly: return_value = math_nextafter_impl(module, x, y, steps); exit: @@ -922,4 +950,4 @@ math_ulp(PyObject *module, PyObject *arg) exit: return return_value; } -/*[clinic end generated code: output=029a00d4b17ac37c input=a9049054013a1b77]*/ +/*[clinic end generated code: output=91a0357265a2a553 input=a9049054013a1b77]*/ diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index eacb2f2ecbca2f..942c07835ccbe4 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -3863,8 +3863,9 @@ math.nextafter x: double y: double - steps: object = None / + * + steps: object = None Return the floating-point value the given number of steps after x towards y. @@ -3876,7 +3877,7 @@ Raises ValueError if steps is negative. static PyObject * math_nextafter_impl(PyObject *module, double x, double y, PyObject *steps) -/*[clinic end generated code: output=cc6511f02afc099e input=fa9d9e3472b55cc1]*/ +/*[clinic end generated code: output=cc6511f02afc099e input=7f2a5842112af2b4]*/ { #if defined(_AIX) if (x == y) { From b45f9c2de01b6fb8e3784d1e9a7b1dd951a87c45 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Wed, 17 May 2023 17:12:45 +0800 Subject: [PATCH 14/19] property based tests (should fail) --- Lib/test/test_math_property.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 Lib/test/test_math_property.py diff --git a/Lib/test/test_math_property.py b/Lib/test/test_math_property.py new file mode 100644 index 00000000000000..8939831cb35a43 --- /dev/null +++ b/Lib/test/test_math_property.py @@ -0,0 +1,29 @@ +from test.support.hypothesis_helper import hypothesis + +floats = hypothesis.strategies.floats +integers = hypothesis.strategies.integers + +from math import nextafter, inf +from functools import reduce + + +def via_reduce(x, y, steps): + return reduce(nextafter, [y] * steps, x) + +class NextafterTests(unittest.TestCase): + @requires_IEEE_754 + @hypothesis.given( + x=floats(), + y=floats(), + a=integers(), + b=integers()) + def test_addition_commutes(self, x, y, a, b): + assert nextafter(nextafter(x, y, steps=a), steps=b) == nextafter(x, y, steps=a+b) + + @requires_IEEE_754 + @hypothesis.given( + x=floats(), + y=floats(), + steps=integers()) + def test_count(self, x, y, steps): + assert via_reduce(x, y, steps) == nextafter(x, y, steps=steps) From 1f9d8907f6cec9fdcd81e516beb9da7ff78fa40c Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Wed, 17 May 2023 17:28:47 +0800 Subject: [PATCH 15/19] Property based tests --- Lib/test/test_math_property.py | 44 ++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/Lib/test/test_math_property.py b/Lib/test/test_math_property.py index 8939831cb35a43..a18a9c6ac2b0fd 100644 --- a/Lib/test/test_math_property.py +++ b/Lib/test/test_math_property.py @@ -1,29 +1,43 @@ +import functools +import unittest +from math import isnan, nextafter +from test.support import requires_IEEE_754 from test.support.hypothesis_helper import hypothesis floats = hypothesis.strategies.floats integers = hypothesis.strategies.integers -from math import nextafter, inf -from functools import reduce + +def equal_float(x, y): + if isnan(x) and isnan(y): + return True + assert x == y def via_reduce(x, y, steps): - return reduce(nextafter, [y] * steps, x) + return functools.reduce(nextafter, [y] * steps, x) + class NextafterTests(unittest.TestCase): @requires_IEEE_754 @hypothesis.given( - x=floats(), - y=floats(), - a=integers(), - b=integers()) - def test_addition_commutes(self, x, y, a, b): - assert nextafter(nextafter(x, y, steps=a), steps=b) == nextafter(x, y, steps=a+b) - + x=floats(), + y=floats(), + steps=integers(min_value=0, max_value=2**16)) + def test_count(self, x, y, steps): + equal_float(via_reduce(x, y, steps), + nextafter(x, y, steps=steps)) + @requires_IEEE_754 @hypothesis.given( - x=floats(), - y=floats(), - steps=integers()) - def test_count(self, x, y, steps): - assert via_reduce(x, y, steps) == nextafter(x, y, steps=steps) + x=floats(), + y=floats(), + a=integers(min_value=0), + b=integers(min_value=0)) + def test_addition_commutes(self, x, y, a, b): + first = nextafter(x, y, steps=a) + second = nextafter(first, y, steps=b) + combined = nextafter(x, y, steps=a+b) + hypothesis.note(f"{first} -> {second} == {combined}") + + equal_float(second, combined) From 215330212e58f003344222e6fbe6fd120fda3074 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Wed, 17 May 2023 17:34:58 +0800 Subject: [PATCH 16/19] simplify --- Lib/test/test_math_property.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_math_property.py b/Lib/test/test_math_property.py index a18a9c6ac2b0fd..7d51aa17b4cc27 100644 --- a/Lib/test/test_math_property.py +++ b/Lib/test/test_math_property.py @@ -8,10 +8,8 @@ integers = hypothesis.strategies.integers -def equal_float(x, y): - if isnan(x) and isnan(y): - return True - assert x == y +def assert_equal_float(x, y): + assert isnan(x) and isnan(y) or x == y def via_reduce(x, y, steps): @@ -25,8 +23,8 @@ class NextafterTests(unittest.TestCase): y=floats(), steps=integers(min_value=0, max_value=2**16)) def test_count(self, x, y, steps): - equal_float(via_reduce(x, y, steps), - nextafter(x, y, steps=steps)) + assert_equal_float(via_reduce(x, y, steps), + nextafter(x, y, steps=steps)) @requires_IEEE_754 @hypothesis.given( @@ -40,4 +38,4 @@ def test_addition_commutes(self, x, y, a, b): combined = nextafter(x, y, steps=a+b) hypothesis.note(f"{first} -> {second} == {combined}") - equal_float(second, combined) + assert_equal_float(second, combined) From 61c7c6e7a89034e7b356b9310249bc64803bc082 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Wed, 17 May 2023 19:47:46 +0800 Subject: [PATCH 17/19] Tickle CI/CD From 6527b77bc21d280f5e1bbc83e139f1e93bdeb693 Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Fri, 19 May 2023 19:26:44 +0100 Subject: [PATCH 18/19] Fix whitespace issue --- Modules/mathmodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 942c07835ccbe4..9e07a12346375a 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -3944,7 +3944,7 @@ math_nextafter_impl(PyObject *module, double x, double y, PyObject *steps) const uint64_t sign_bit = 1ULL<<63; uint64_t ax = ux.i & ~sign_bit; - uint64_t ay = uy.i & ~sign_bit; + uint64_t ay = uy.i & ~sign_bit; // opposite signs if (((ux.i ^ uy.i) & sign_bit)) { From 66ef3f968037c693fec059aa85f4740bf4ddf04f Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Fri, 19 May 2023 20:27:25 +0100 Subject: [PATCH 19/19] Portability fix and a minor style fix --- Modules/mathmodule.c | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 9e07a12346375a..f26602d5871acc 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -3908,19 +3908,29 @@ math_nextafter_impl(PyObject *module, double x, double y, PyObject *steps) return NULL; } - uint64_t usteps = PyLong_AsUnsignedLongLong(steps); + unsigned long long usteps_ull = PyLong_AsUnsignedLongLong(steps); // Conveniently, uint64_t and double have the same number of bits // on all the platforms we care about. // So if an overflow occurs, we can just use UINT64_MAX. - if (usteps == (unsigned long long)-1 && PyErr_Occurred()) { - if(!PyErr_ExceptionMatches(PyExc_OverflowError)) { - Py_DECREF(steps); - return NULL; + Py_DECREF(steps); + if (usteps_ull >= UINT64_MAX) { + // This branch includes the case where an error occurred, since + // (unsigned long long)(-1) = ULLONG_MAX >= UINT64_MAX. Note that + // usteps_ull can be strictly larger than UINT64_MAX on a machine + // where unsigned long long has width > 64 bits. + if (PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_OverflowError)) { + PyErr_Clear(); + } + else { + return NULL; + } } - PyErr_Clear(); - usteps = UINT64_MAX; + usteps_ull = UINT64_MAX; } - Py_DECREF(steps); + assert(usteps_ull <= UINT64_MAX); + uint64_t usteps = (uint64_t)usteps_ull; + if (usteps == 0) { return PyFloat_FromDouble(x); } @@ -3937,7 +3947,7 @@ math_nextafter_impl(PyObject *module, double x, double y, PyObject *steps) // would be a "mixed-endian" double.) union pun {double f; uint64_t i;}; union pun ux = {x}, uy = {y}; - if(ux.i == uy.i) { + if (ux.i == uy.i) { return PyFloat_FromDouble(x); }