Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 34k
gh-94906: Support multiple steps in math.nextafter#103881
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
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
77fb4ae5e979780f2b30fb90e86caccc22f8e1db3968733471fff8b4311f266cd70feae0421b6a8c83a788c46c42232bb4a2724f6b7b5ac6b45f9c21f9d890215330261c7c6e04af22b6527b7766ef3f9File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -224,11 +224,11 @@ 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*. | ||
matthiasgoergens marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| If *x* is equal to *y*, return *y*. | ||
| If *x* is equal to *y*, return *y*, unless *steps* is zero. | ||
| Examples: | ||
| @@ -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) | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| 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 | ||
| def assert_equal_float(x, y): | ||
| assert isnan(x) and isnan(y) or x == y | ||
| def via_reduce(x, y, steps): | ||
| return functools.reduce(nextafter, [y] * steps, x) | ||
| class NextafterTests(unittest.TestCase): | ||
| @requires_IEEE_754 | ||
| @hypothesis.given( | ||
| x=floats(), | ||
| y=floats(), | ||
| steps=integers(min_value=0, max_value=2**16)) | ||
| def test_count(self, x, y, steps): | ||
| assert_equal_float(via_reduce(x, y, steps), | ||
| nextafter(x, y, steps=steps)) | ||
| @requires_IEEE_754 | ||
| @hypothesis.given( | ||
| 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}") | ||
| assert_equal_float(second, combined) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Support multiple steps in :func:`math.nextafter`. Patch by Shantanu Jain and Matthias Gorgens. |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -3864,13 +3864,20 @@ math.nextafter | ||
| x: double | ||
| y: double | ||
| / | ||
| * | ||
| 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. | ||
| Return the next floating-point value after x towards y. | ||
| 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) | ||
| /*[clinic end generated code: output=750c8266c1c540ce input=02b2d50cd1d9f9b6]*/ | ||
| 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){ | ||
| @@ -3885,7 +3892,101 @@ math_nextafter_impl(PyObject *module, double x, double y) | ||
| return PyFloat_FromDouble(y); | ||
| } | ||
| #endif | ||
| return PyFloat_FromDouble(nextafter(x, y)); | ||
| if (steps == Py_None){ | ||
| // fast path: we default to one step. | ||
| return PyFloat_FromDouble(nextafter(x, y)); | ||
| } | ||
| steps = PyNumber_Index(steps); | ||
| if (steps == NULL){ | ||
| return NULL; | ||
| } | ||
| assert(PyLong_CheckExact(steps)); | ||
| if (_PyLong_IsNegative((PyLongObject *)steps)){ | ||
| PyErr_SetString(PyExc_ValueError, | ||
| "steps must be a non-negative integer"); | ||
| Py_DECREF(steps); | ||
| return NULL; | ||
| } | ||
| 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. | ||
| 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; | ||
| } | ||
| } | ||
| usteps_ull = UINT64_MAX; | ||
| } | ||
| assert(usteps_ull <= UINT64_MAX); | ||
| uint64_t usteps = (uint64_t)usteps_ull; | ||
| if (usteps == 0){ | ||
| return PyFloat_FromDouble(x); | ||
| } | ||
| if (Py_IS_NAN(x)){ | ||
| return PyFloat_FromDouble(x); | ||
| } | ||
| if (Py_IS_NAN(y)){ | ||
| return PyFloat_FromDouble(y); | ||
| } | ||
| // 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}; | ||
matthiasgoergens marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| 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)){ | ||
| // NOTE: ax + ay can never overflow, because their 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 | ||
| // 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){ | ||
| if (ax - ay >= usteps){ | ||
| ux.i -= usteps; | ||
| return PyFloat_FromDouble(ux.f); | ||
| } else{ | ||
| return PyFloat_FromDouble(uy.f); | ||
| } | ||
| } else{ | ||
| if (ay - ax >= usteps){ | ||
| ux.i += usteps; | ||
| return PyFloat_FromDouble(ux.f); | ||
| } else{ | ||
| return PyFloat_FromDouble(uy.f); | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.