mirror of
https://github.com/python/cpython.git
synced 2025-08-31 05:58:33 +00:00
gh-94906: Support multiple steps in math.nextafter (#103881)
This PR updates `math.nextafter` to add a new `steps` argument. The behaviour is as though `math.nextafter` had been called `steps` times in succession. --------- Co-authored-by: Mark Dickinson <mdickinson@enthought.com>
This commit is contained in:
parent
c3f43bfb4b
commit
6e39fa1955
10 changed files with 223 additions and 18 deletions
55
Modules/clinic/mathmodule.c.h
generated
55
Modules/clinic/mathmodule.c.h
generated
|
@ -826,25 +826,59 @@ exit:
|
|||
}
|
||||
|
||||
PyDoc_STRVAR(math_nextafter__doc__,
|
||||
"nextafter($module, x, y, /)\n"
|
||||
"nextafter($module, x, y, /, *, steps=None)\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.\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, 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, 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, 2)) {
|
||||
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 0, argsbuf);
|
||||
if (!args) {
|
||||
goto exit;
|
||||
}
|
||||
if (PyFloat_CheckExact(args[0])) {
|
||||
|
@ -867,7 +901,12 @@ 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 = args[2];
|
||||
skip_optional_kwonly:
|
||||
return_value = math_nextafter_impl(module, x, y, steps);
|
||||
|
||||
exit:
|
||||
return return_value;
|
||||
|
@ -911,4 +950,4 @@ math_ulp(PyObject *module, PyObject *arg)
|
|||
exit:
|
||||
return return_value;
|
||||
}
|
||||
/*[clinic end generated code: output=a6437a3ba18c486a input=a9049054013a1b77]*/
|
||||
/*[clinic end generated code: output=91a0357265a2a553 input=a9049054013a1b77]*/
|
||||
|
|
|
@ -3864,13 +3864,20 @@ math.nextafter
|
|||
x: double
|
||||
y: double
|
||||
/
|
||||
*
|
||||
steps: object = None
|
||||
|
||||
Return the next floating-point value after x towards y.
|
||||
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)
|
||||
/*[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;};
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue