From: Alexander Belopolsky Date: Sun, 4 Aug 2013 18:51:35 +0000 (-0400) Subject: Fixes #8860: Round half-microseconds to even in the timedelta constructor. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=790d269d393fe625614db5cc8fe8ad0d4029253d;p=python Fixes #8860: Round half-microseconds to even in the timedelta constructor. (Original patch by Mark Dickinson.) --- diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 9d0af67844..5bcac47427 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -170,10 +170,12 @@ dates or times. * ``0 <= seconds < 3600*24`` (the number of seconds in one day) * ``-999999999 <= days <= 999999999`` - If any argument is a float and there are fractional microseconds, the fractional - microseconds left over from all arguments are combined and their sum is rounded - to the nearest microsecond. If no argument is a float, the conversion and - normalization processes are exact (no information is lost). + If any argument is a float and there are fractional microseconds, + the fractional microseconds left over from all arguments are + combined and their sum is rounded to the nearest microsecond using + 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, :exc:`OverflowError` is raised. diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 931ef6fc03..e08b2dc24f 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -619,6 +619,10 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase): eq(td(hours=-.2/us_per_hour), td(0)) 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): td = timedelta(microseconds=-1) self.assertEqual((td.days, td.seconds, td.microseconds), diff --git a/Misc/NEWS b/Misc/NEWS index 84ce19fffa..04bd92f53e 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -13,6 +13,7 @@ Core and Builtins Library ------- +- Issue 8860: Fixed rounding in timedelta constructor. What's New in Python 3.4.0 Alpha 1? =================================== diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 34205a4391..d3929c7563 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -140,19 +140,6 @@ divmod(int x, int y, int *r) 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 * are rounded to even. */ @@ -1397,7 +1384,7 @@ cmperror(PyObject *a, PyObject *b) */ /* 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_second = NULL; /* 1000000 */ static PyObject *us_per_minute = NULL; /* 1e6 * 60 as Python int */ @@ -2119,7 +2106,7 @@ delta_new(PyTypeObject *type, PyObject *args, PyObject *kw) goto Done if (us) { - y = accum("microseconds", x, us, us_per_us, &leftover_us); + y = accum("microseconds", x, us, one, &leftover_us); CLEANUP; } if (ms) { @@ -2148,7 +2135,33 @@ delta_new(PyTypeObject *type, PyObject *args, PyObject *kw) } if (leftover_us) { /* 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) { Py_DECREF(x); goto Done; @@ -5351,12 +5364,12 @@ PyInit__datetime(void) assert(DI100Y == 25 * DI4Y - 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_second = PyLong_FromLong(1000000); us_per_minute = PyLong_FromLong(60000000); 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) return NULL;