]> granicus.if.org Git - python/commitdiff
Fixes #8860: Round half-microseconds to even in the timedelta constructor.
authorAlexander Belopolsky <alexander.belopolsky@gmail.com>
Sun, 4 Aug 2013 18:51:35 +0000 (14:51 -0400)
committerAlexander Belopolsky <alexander.belopolsky@gmail.com>
Sun, 4 Aug 2013 18:51:35 +0000 (14:51 -0400)
(Original patch by Mark Dickinson.)

Doc/library/datetime.rst
Lib/test/datetimetester.py
Misc/NEWS
Modules/_datetimemodule.c

index 9d0af678446146535baa958da656995871b349cb..5bcac474273612baf2f00a7c68af7b44c6498707 100644 (file)
@@ -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.
index 931ef6fc03d4554fe8a2523b5f36fc029c75b867..e08b2dc24f0419f87a3f30f9bf7ea78b807708c0 100644 (file)
@@ -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),
index 84ce19fffad12eb1c8266ed0556a3712d82784d3..04bd92f53e6a6e6f89f83d4b30a46d90cffc7171 100644 (file)
--- 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?
 ===================================
index 34205a4391c2a309c9feb729eb60728395868194..d3929c75639e79dc9fc0e07eadb64d0fa9a12d9b 100644 (file)
@@ -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;