Skip to content

Commit 77fb4ae

Browse files
pythongh-94906: Support multiple steps in math.nextafter
1 parent 1c0a9c5 commit 77fb4ae

9 files changed

+138
-17
lines changed

Doc/library/math.rst

+5-2
Original file line numberDiff line numberDiff line change
@@ -224,9 +224,9 @@ Number-theoretic and representation functions
224224
of *x* and are floats.
225225

226226

227-
.. function:: nextafter(x, y)
227+
.. function:: nextafter(x, y, /, *, steps=1)
228228

229-
Return the next floating-point value after *x* towards *y*.
229+
Return the floating-point value *steps* steps after *x* towards *y*.
230230

231231
If *x* is equal to *y*, return *y*.
232232

@@ -239,6 +239,9 @@ Number-theoretic and representation functions
239239

240240
See also :func:`math.ulp`.
241241

242+
.. versionchanged:: 3.12
243+
Added the *steps* argument.
244+
242245
.. versionadded:: 3.9
243246

244247
.. function:: perm(n, k=None)

Include/internal/pycore_global_objects_fini_generated.h

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_global_strings.h

+1
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,7 @@ struct _Py_global_strings {
657657
STRUCT_FOR_ID(stdin)
658658
STRUCT_FOR_ID(stdout)
659659
STRUCT_FOR_ID(step)
660+
STRUCT_FOR_ID(steps)
660661
STRUCT_FOR_ID(store_name)
661662
STRUCT_FOR_ID(strategy)
662663
STRUCT_FOR_ID(strftime)

Include/internal/pycore_runtime_init_generated.h

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_unicodeobject_generated.h

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/test/test_math.py

+17-3
Original file line numberDiff line numberDiff line change
@@ -2296,11 +2296,20 @@ def test_nextafter(self):
22962296
float.fromhex('0x1.fffffffffffffp-1'))
22972297
self.assertEqual(math.nextafter(1.0, INF),
22982298
float.fromhex('0x1.0000000000001p+0'))
2299+
self.assertEqual(math.nextafter(1.0, -INF, steps=1),
2300+
float.fromhex('0x1.fffffffffffffp-1'))
2301+
self.assertEqual(math.nextafter(1.0, INF, steps=1),
2302+
float.fromhex('0x1.0000000000001p+0'))
2303+
self.assertEqual(math.nextafter(1.0, -INF, steps=3),
2304+
float.fromhex('0x1.ffffffffffffdp-1'))
2305+
self.assertEqual(math.nextafter(1.0, INF, steps=3),
2306+
float.fromhex('0x1.0000000000003p+0'))
22992307

23002308
# x == y: y is returned
2301-
self.assertEqual(math.nextafter(2.0, 2.0), 2.0)
2302-
self.assertEqualSign(math.nextafter(-0.0, +0.0), +0.0)
2303-
self.assertEqualSign(math.nextafter(+0.0, -0.0), -0.0)
2309+
for steps in range(1, 5):
2310+
self.assertEqual(math.nextafter(2.0, 2.0, steps=steps), 2.0)
2311+
self.assertEqualSign(math.nextafter(-0.0, +0.0, steps=steps), +0.0)
2312+
self.assertEqualSign(math.nextafter(+0.0, -0.0, steps=steps), -0.0)
23042313

23052314
# around 0.0
23062315
smallest_subnormal = sys.float_info.min * sys.float_info.epsilon
@@ -2325,6 +2334,11 @@ def test_nextafter(self):
23252334
self.assertIsNaN(math.nextafter(1.0, NAN))
23262335
self.assertIsNaN(math.nextafter(NAN, NAN))
23272336

2337+
self.assertEqual(1.0, math.nextafter(1.0, INF, steps=0))
2338+
with self.assertRaises(ValueError):
2339+
math.nextafter(1.0, INF, steps=-1)
2340+
2341+
23282342
@requires_IEEE_754
23292343
def test_ulp(self):
23302344
self.assertEqual(math.ulp(1.0), sys.float_info.epsilon)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Support multiple steps in :func:`math.nextafter`. Patch by Shantanu Jain and Matthias Gorgens.

Modules/clinic/mathmodule.c.h

+45-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/mathmodule.c

+64-4
Original file line numberDiff line numberDiff line change
@@ -3893,13 +3893,15 @@ math.nextafter
38933893
x: double
38943894
y: double
38953895
/
3896+
*
3897+
steps: int = 1
38963898
3897-
Return the next floating-point value after x towards y.
3899+
Return the floating-point value the given number of steps after x towards y.
38983900
[clinic start generated code]*/
38993901

39003902
static PyObject *
3901-
math_nextafter_impl(PyObject *module, double x, double y)
3902-
/*[clinic end generated code: output=750c8266c1c540ce input=02b2d50cd1d9f9b6]*/
3903+
math_nextafter_impl(PyObject *module, double x, double y, int steps)
3904+
/*[clinic end generated code: output=14190eb869199e5a input=a794e7a79768ee25]*/
39033905
{
39043906
#if defined(_AIX)
39053907
if (x == y) {
@@ -3914,7 +3916,65 @@ math_nextafter_impl(PyObject *module, double x, double y)
39143916
return PyFloat_FromDouble(y);
39153917
}
39163918
#endif
3917-
return PyFloat_FromDouble(nextafter(x, y));
3919+
// fast path:
3920+
if (steps == 1) {
3921+
return PyFloat_FromDouble(nextafter(x, y));
3922+
}
3923+
if (steps < 0) {
3924+
PyErr_SetString(PyExc_ValueError, "steps must be >= 0");
3925+
return NULL;
3926+
}
3927+
if (steps == 0)
3928+
return PyFloat_FromDouble(x);
3929+
if (Py_IS_NAN(x) || Py_IS_NAN(y))
3930+
return PyFloat_FromDouble(x+y);
3931+
3932+
uint64_t usteps = steps;
3933+
3934+
union pun {double f; uint64_t i;};
3935+
union pun ux = {x}, uy = {y};
3936+
if(ux.i == uy.i) {
3937+
return PyFloat_FromDouble(x);
3938+
}
3939+
3940+
const uint64_t sign_bit = 1ULL<<63;
3941+
3942+
uint64_t ax = ux.i & ~sign_bit;
3943+
uint64_t ay = uy.i & ~sign_bit;
3944+
3945+
// opposite signs
3946+
if (((ux.i ^ uy.i) & sign_bit)) {
3947+
if (ax + ay <= usteps) {
3948+
return PyFloat_FromDouble(uy.f);
3949+
// This comparison has to use <, because <= would get +0.0 vs -0.0
3950+
// wrong.
3951+
} else if (ax < usteps) {
3952+
union pun result = {.i = (uy.i & sign_bit) | (usteps - ax)};
3953+
return PyFloat_FromDouble(result.f);
3954+
} else {
3955+
ux.i -= usteps;
3956+
return PyFloat_FromDouble(ux.f);
3957+
}
3958+
// same sign
3959+
} else if (ax > ay) {
3960+
// the addition is not UB,
3961+
// because we have an extra bit at the top of ax and usteps.
3962+
if (ax >= ay + usteps) {
3963+
ux.i -= usteps;
3964+
return PyFloat_FromDouble(ux.f);
3965+
} else {
3966+
return PyFloat_FromDouble(uy.f);
3967+
}
3968+
} else {
3969+
// the addition is not UB,
3970+
// because we have an extra bit at the top of ax and usteps.
3971+
if (ax + usteps <= ay) {
3972+
ux.i += usteps;
3973+
return PyFloat_FromDouble(ux.f);
3974+
} else {
3975+
return PyFloat_FromDouble(uy.f);
3976+
}
3977+
}
39183978
}
39193979

39203980

0 commit comments

Comments
 (0)