Commit cf02335a authored by Mark Dickinson's avatar Mark Dickinson

Issue #8817: Expose round-to-nearest variant of divmod in _PyLong_Divmod_Near

for use by the datetime module; also refactor long_round to use this function.
parent 76d359a8
...@@ -101,6 +101,14 @@ PyAPI_FUNC(int) _PyLong_Sign(PyObject *v); ...@@ -101,6 +101,14 @@ PyAPI_FUNC(int) _PyLong_Sign(PyObject *v);
*/ */
PyAPI_FUNC(size_t) _PyLong_NumBits(PyObject *v); PyAPI_FUNC(size_t) _PyLong_NumBits(PyObject *v);
/* _PyLong_Divmod_Near. Given integers a and b, compute the nearest
integer q to the exact quotient a / b, rounding to the nearest even integer
in the case of a tie. Return (q, r), where r = a - q*b. The remainder r
will satisfy abs(r) <= abs(b)/2, with equality possible only if q is
even.
*/
PyAPI_FUNC(PyObject *) _PyLong_Divmod_Near(PyObject *, PyObject *);
/* _PyLong_FromByteArray: View the n unsigned bytes as a binary integer in /* _PyLong_FromByteArray: View the n unsigned bytes as a binary integer in
base 256, and return a Python long with the same numeric value. base 256, and return a Python long with the same numeric value.
If n is 0, the integer is 0. Else: If n is 0, the integer is 0. Else:
......
...@@ -4201,140 +4201,169 @@ long__format__(PyObject *self, PyObject *args) ...@@ -4201,140 +4201,169 @@ long__format__(PyObject *self, PyObject *args)
PyUnicode_GET_SIZE(format_spec)); PyUnicode_GET_SIZE(format_spec));
} }
/* Return a pair (q, r) such that a = b * q + r, and
abs(r) <= abs(b)/2, with equality possible only if q is even.
In other words, q == a / b, rounded to the nearest integer using
round-half-to-even. */
PyObject *
_PyLong_Divmod_Near(PyObject *a, PyObject *b)
{
PyLongObject *quo = NULL, *rem = NULL;
PyObject *one = NULL, *twice_rem, *result, *temp;
int cmp, quo_is_odd, quo_is_neg;
/* Equivalent Python code:
def divmod_near(a, b):
q, r = divmod(a, b)
# round up if either r / b > 0.5, or r / b == 0.5 and q is odd.
# The expression r / b > 0.5 is equivalent to 2 * r > b if b is
# positive, 2 * r < b if b negative.
greater_than_half = 2*r > b if b > 0 else 2*r < b
exactly_half = 2*r == b
if greater_than_half or exactly_half and q % 2 == 1:
q += 1
r -= b
return q, r
*/
if (!PyLong_Check(a) || !PyLong_Check(b)) {
PyErr_SetString(PyExc_TypeError,
"non-integer arguments in division");
return NULL;
}
/* Do a and b have different signs? If so, quotient is negative. */
quo_is_neg = (Py_SIZE(a) < 0) != (Py_SIZE(b) < 0);
one = PyLong_FromLong(1L);
if (one == NULL)
return NULL;
if (long_divrem((PyLongObject*)a, (PyLongObject*)b, &quo, &rem) < 0)
goto error;
/* compare twice the remainder with the divisor, to see
if we need to adjust the quotient and remainder */
twice_rem = long_lshift((PyObject *)rem, one);
if (twice_rem == NULL)
goto error;
if (quo_is_neg) {
temp = long_neg((PyLongObject*)twice_rem);
Py_DECREF(twice_rem);
twice_rem = temp;
if (twice_rem == NULL)
goto error;
}
cmp = long_compare((PyLongObject *)twice_rem, (PyLongObject *)b);
Py_DECREF(twice_rem);
quo_is_odd = Py_SIZE(quo) != 0 && ((quo->ob_digit[0] & 1) != 0);
if ((Py_SIZE(b) < 0 ? cmp < 0 : cmp > 0) || (cmp == 0 && quo_is_odd)) {
/* fix up quotient */
if (quo_is_neg)
temp = long_sub(quo, (PyLongObject *)one);
else
temp = long_add(quo, (PyLongObject *)one);
Py_DECREF(quo);
quo = (PyLongObject *)temp;
if (quo == NULL)
goto error;
/* and remainder */
if (quo_is_neg)
temp = long_add(rem, (PyLongObject *)b);
else
temp = long_sub(rem, (PyLongObject *)b);
Py_DECREF(rem);
rem = (PyLongObject *)temp;
if (rem == NULL)
goto error;
}
result = PyTuple_New(2);
if (result == NULL)
goto error;
/* PyTuple_SET_ITEM steals references */
PyTuple_SET_ITEM(result, 0, (PyObject *)quo);
PyTuple_SET_ITEM(result, 1, (PyObject *)rem);
Py_DECREF(one);
return result;
error:
Py_XDECREF(quo);
Py_XDECREF(rem);
Py_XDECREF(one);
return NULL;
}
static PyObject * static PyObject *
long_round(PyObject *self, PyObject *args) long_round(PyObject *self, PyObject *args)
{ {
PyObject *o_ndigits=NULL, *temp; PyObject *o_ndigits=NULL, *temp, *result, *ndigits;
PyLongObject *pow=NULL, *q=NULL, *r=NULL, *ndigits=NULL, *one;
int errcode; /* To round an integer m to the nearest 10**n (n positive), we make use of
digit q_mod_4; * the divmod_near operation, defined by:
*
/* Notes on the algorithm: to round to the nearest 10**n (n positive), * divmod_near(a, b) = (q, r)
the straightforward method is: *
* where q is the nearest integer to the quotient a / b (the
(1) divide by 10**n * nearest even integer in the case of a tie) and r == a - q * b.
(2) round to nearest integer (round to even in case of tie) * Hence q * b = a - r is the nearest multiple of b to a,
(3) multiply result by 10**n. * preferring even multiples in the case of a tie.
*
But the rounding step involves examining the fractional part of the * So the nearest multiple of 10**n to m is:
quotient to see whether it's greater than 0.5 or not. Since we *
want to do the whole calculation in integer arithmetic, it's * m - divmod_near(m, 10**n)[1].
simpler to do:
(1) divide by (10**n)/2
(2) round to nearest multiple of 2 (multiple of 4 in case of tie)
(3) multiply result by (10**n)/2.
Then all we need to know about the fractional part of the quotient
arising in step (2) is whether it's zero or not.
Doing both a multiplication and division is wasteful, and is easily
avoided if we just figure out how much to adjust the original input
by to do the rounding.
Here's the whole algorithm expressed in Python.
def round(self, ndigits = None):
"""round(int, int) -> int"""
if ndigits is None or ndigits >= 0:
return self
pow = 10**-ndigits >> 1
q, r = divmod(self, pow)
self -= r
if (q & 1 != 0):
if (q & 2 == r == 0):
self -= pow
else:
self += pow
return self
*/ */
if (!PyArg_ParseTuple(args, "|O", &o_ndigits)) if (!PyArg_ParseTuple(args, "|O", &o_ndigits))
return NULL; return NULL;
if (o_ndigits == NULL) if (o_ndigits == NULL)
return long_long(self); return long_long(self);
ndigits = (PyLongObject *)PyNumber_Index(o_ndigits); ndigits = PyNumber_Index(o_ndigits);
if (ndigits == NULL) if (ndigits == NULL)
return NULL; return NULL;
/* if ndigits >= 0 then no rounding is necessary; return self unchanged */
if (Py_SIZE(ndigits) >= 0) { if (Py_SIZE(ndigits) >= 0) {
Py_DECREF(ndigits); Py_DECREF(ndigits);
return long_long(self); return long_long(self);
} }
Py_INCREF(self); /* to keep refcounting simple */ /* result = self - divmod_near(self, 10 ** -ndigits)[1] */
/* we now own references to self, ndigits */ temp = long_neg((PyLongObject*)ndigits);
/* pow = 10 ** -ndigits >> 1 */
pow = (PyLongObject *)PyLong_FromLong(10L);
if (pow == NULL)
goto error;
temp = long_neg(ndigits);
Py_DECREF(ndigits); Py_DECREF(ndigits);
ndigits = (PyLongObject *)temp; ndigits = temp;
if (ndigits == NULL) if (ndigits == NULL)
goto error; return NULL;
temp = long_pow((PyObject *)pow, (PyObject *)ndigits, Py_None);
Py_DECREF(pow);
pow = (PyLongObject *)temp;
if (pow == NULL)
goto error;
assert(PyLong_Check(pow)); /* check long_pow returned a long */
one = (PyLongObject *)PyLong_FromLong(1L);
if (one == NULL)
goto error;
temp = long_rshift(pow, one);
Py_DECREF(one);
Py_DECREF(pow);
pow = (PyLongObject *)temp;
if (pow == NULL)
goto error;
/* q, r = divmod(self, pow) */
errcode = l_divmod((PyLongObject *)self, pow, &q, &r);
if (errcode == -1)
goto error;
/* self -= r */
temp = long_sub((PyLongObject *)self, r);
Py_DECREF(self);
self = temp;
if (self == NULL)
goto error;
/* get value of quotient modulo 4 */
if (Py_SIZE(q) == 0)
q_mod_4 = 0;
else if (Py_SIZE(q) > 0)
q_mod_4 = q->ob_digit[0] & 3;
else
q_mod_4 = (PyLong_BASE-q->ob_digit[0]) & 3;
if ((q_mod_4 & 1) == 1) { result = PyLong_FromLong(10L);
/* q is odd; round self up or down by adding or subtracting pow */ if (result == NULL) {
if (q_mod_4 == 1 && Py_SIZE(r) == 0) Py_DECREF(ndigits);
temp = (PyObject *)long_sub((PyLongObject *)self, pow); return NULL;
else
temp = (PyObject *)long_add((PyLongObject *)self, pow);
Py_DECREF(self);
self = temp;
if (self == NULL)
goto error;
} }
Py_DECREF(q);
Py_DECREF(r); temp = long_pow(result, ndigits, Py_None);
Py_DECREF(pow);
Py_DECREF(ndigits); Py_DECREF(ndigits);
return self; Py_DECREF(result);
result = temp;
if (result == NULL)
return NULL;
error: temp = _PyLong_Divmod_Near(self, result);
Py_XDECREF(q); Py_DECREF(result);
Py_XDECREF(r); result = temp;
Py_XDECREF(pow); if (result == NULL)
Py_XDECREF(self);
Py_XDECREF(ndigits);
return NULL; return NULL;
temp = long_sub((PyLongObject *)self,
(PyLongObject *)PyTuple_GET_ITEM(result, 1));
Py_DECREF(result);
result = temp;
return result;
} }
static PyObject * static PyObject *
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment