Skip to content
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

gh-94906: Support multiple steps in math.nextafter #94908

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions Doc/library/math.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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*.

Expand All @@ -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)
Expand Down
21 changes: 18 additions & 3 deletions Lib/test/test_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support multiple steps in :func:`math.nextafter`. Patch by Shantanu Jain.
30 changes: 22 additions & 8 deletions Modules/clinic/mathmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 14 additions & 4 deletions Modules/mathmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can allow zero steps just fine.

PyErr_SetString(PyExc_ValueError, "steps must be >= 1");
return NULL;
}
for (i = 0; i < steps; i++) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can support this without a loop. See this pure Python prototype for how that can work.

x = nextafter(x, y);
}
return PyFloat_FromDouble(x);
}


Expand Down