Commit 6bb0625e authored by Alexander Belopolsky's avatar Alexander Belopolsky

Fixes #8860: Round half-microseconds to even in the timedelta constructor.

(Original patch by Mark Dickinson.)
parent 681a0a4c
...@@ -170,10 +170,12 @@ dates or times. ...@@ -170,10 +170,12 @@ dates or times.
* ``0 <= seconds < 3600*24`` (the number of seconds in one day) * ``0 <= seconds < 3600*24`` (the number of seconds in one day)
* ``-999999999 <= days <= 999999999`` * ``-999999999 <= days <= 999999999``
If any argument is a float and there are fractional microseconds, the fractional If any argument is a float and there are fractional microseconds,
microseconds left over from all arguments are combined and their sum is rounded the fractional microseconds left over from all arguments are
to the nearest microsecond. If no argument is a float, the conversion and combined and their sum is rounded to the nearest microsecond using
normalization processes are exact (no information is lost). round-half-to-even tiebreaker. If no argument is a float, the
conversion and normalization processes are exact (no information is
lost).
If the normalized value of days lies outside the indicated range, If the normalized value of days lies outside the indicated range,
:exc:`OverflowError` is raised. :exc:`OverflowError` is raised.
......
...@@ -619,6 +619,10 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase): ...@@ -619,6 +619,10 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
eq(td(hours=-.2/us_per_hour), td(0)) eq(td(hours=-.2/us_per_hour), td(0))
eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1)) eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
# Test for a patch in Issue 8860
eq(td(microseconds=0.5), 0.5*td(microseconds=1.0))
eq(td(microseconds=0.5)//td.resolution, 0.5*td.resolution//td.resolution)
def test_massive_normalization(self): def test_massive_normalization(self):
td = timedelta(microseconds=-1) td = timedelta(microseconds=-1)
self.assertEqual((td.days, td.seconds, td.microseconds), self.assertEqual((td.days, td.seconds, td.microseconds),
......
...@@ -13,6 +13,7 @@ Core and Builtins ...@@ -13,6 +13,7 @@ Core and Builtins
Library Library
------- -------
- Issue 8860: Fixed rounding in timedelta constructor.
What's New in Python 3.4.0 Alpha 1? What's New in Python 3.4.0 Alpha 1?
=================================== ===================================
......
...@@ -140,19 +140,6 @@ divmod(int x, int y, int *r) ...@@ -140,19 +140,6 @@ divmod(int x, int y, int *r)
return quo; return quo;
} }
/* Round a double to the nearest long. |x| must be small enough to fit
* in a C long; this is not checked.
*/
static long
round_to_long(double x)
{
if (x >= 0.0)
x = floor(x + 0.5);
else
x = ceil(x - 0.5);
return (long)x;
}
/* Nearest integer to m / n for integers m and n. Half-integer results /* Nearest integer to m / n for integers m and n. Half-integer results
* are rounded to even. * are rounded to even.
*/ */
...@@ -1397,7 +1384,7 @@ cmperror(PyObject *a, PyObject *b) ...@@ -1397,7 +1384,7 @@ cmperror(PyObject *a, PyObject *b)
*/ */
/* Conversion factors. */ /* Conversion factors. */
static PyObject *us_per_us = NULL; /* 1 */ static PyObject *one = NULL; /* 1 */
static PyObject *us_per_ms = NULL; /* 1000 */ static PyObject *us_per_ms = NULL; /* 1000 */
static PyObject *us_per_second = NULL; /* 1000000 */ static PyObject *us_per_second = NULL; /* 1000000 */
static PyObject *us_per_minute = NULL; /* 1e6 * 60 as Python int */ static PyObject *us_per_minute = NULL; /* 1e6 * 60 as Python int */
...@@ -2119,7 +2106,7 @@ delta_new(PyTypeObject *type, PyObject *args, PyObject *kw) ...@@ -2119,7 +2106,7 @@ delta_new(PyTypeObject *type, PyObject *args, PyObject *kw)
goto Done goto Done
if (us) { if (us) {
y = accum("microseconds", x, us, us_per_us, &leftover_us); y = accum("microseconds", x, us, one, &leftover_us);
CLEANUP; CLEANUP;
} }
if (ms) { if (ms) {
...@@ -2148,7 +2135,33 @@ delta_new(PyTypeObject *type, PyObject *args, PyObject *kw) ...@@ -2148,7 +2135,33 @@ delta_new(PyTypeObject *type, PyObject *args, PyObject *kw)
} }
if (leftover_us) { if (leftover_us) {
/* Round to nearest whole # of us, and add into x. */ /* Round to nearest whole # of us, and add into x. */
PyObject *temp = PyLong_FromLong(round_to_long(leftover_us)); double whole_us = round(leftover_us);
int x_is_odd;
PyObject *temp;
whole_us = round(leftover_us);
if (fabs(whole_us - leftover_us) == 0.5) {
/* We're exactly halfway between two integers. In order
* to do round-half-to-even, we must determine whether x
* is odd. Note that x is odd when it's last bit is 1. The
* code below uses bitwise and operation to check the last
* bit. */
temp = PyNumber_And(x, one); /* temp <- x & 1 */
if (temp == NULL) {
Py_DECREF(x);
goto Done;
}
x_is_odd = PyObject_IsTrue(temp);
Py_DECREF(temp);
if (x_is_odd == -1) {
Py_DECREF(x);
goto Done;
}
whole_us = 2.0 * round((leftover_us + x_is_odd) * 0.5) - x_is_odd;
}
temp = PyLong_FromLong(whole_us);
if (temp == NULL) { if (temp == NULL) {
Py_DECREF(x); Py_DECREF(x);
goto Done; goto Done;
...@@ -5351,12 +5364,12 @@ PyInit__datetime(void) ...@@ -5351,12 +5364,12 @@ PyInit__datetime(void)
assert(DI100Y == 25 * DI4Y - 1); assert(DI100Y == 25 * DI4Y - 1);
assert(DI100Y == days_before_year(100+1)); assert(DI100Y == days_before_year(100+1));
us_per_us = PyLong_FromLong(1); one = PyLong_FromLong(1);
us_per_ms = PyLong_FromLong(1000); us_per_ms = PyLong_FromLong(1000);
us_per_second = PyLong_FromLong(1000000); us_per_second = PyLong_FromLong(1000000);
us_per_minute = PyLong_FromLong(60000000); us_per_minute = PyLong_FromLong(60000000);
seconds_per_day = PyLong_FromLong(24 * 3600); seconds_per_day = PyLong_FromLong(24 * 3600);
if (us_per_us == NULL || us_per_ms == NULL || us_per_second == NULL || if (one == NULL || us_per_ms == NULL || us_per_second == NULL ||
us_per_minute == NULL || seconds_per_day == NULL) us_per_minute == NULL || seconds_per_day == NULL)
return NULL; return NULL;
......
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