]> granicus.if.org Git - python/commitdiff
Issue #23517: datetime.timedelta constructor now rounds microseconds to nearest
authorVictor Stinner <victor.stinner@gmail.com>
Wed, 2 Sep 2015 17:16:07 +0000 (19:16 +0200)
committerVictor Stinner <victor.stinner@gmail.com>
Wed, 2 Sep 2015 17:16:07 +0000 (19:16 +0200)
with ties going away from zero (ROUND_HALF_UP), as Python 2 and Python older
than 3.3, instead of rounding to nearest with ties going to nearest even
integer (ROUND_HALF_EVEN).

Include/pytime.h
Lib/datetime.py
Lib/test/datetimetester.py
Misc/NEWS
Modules/_datetimemodule.c
Python/pytime.c

index 98ae12bae52335a87fa1d81859f58ffbe8208ba2..41fb80607dba91a858764943dbd822037c9d6823 100644 (file)
@@ -44,6 +44,10 @@ PyAPI_FUNC(PyObject *) _PyLong_FromTime_t(
 PyAPI_FUNC(time_t) _PyLong_AsTime_t(
     PyObject *obj);
 
+/* Round to nearest with ties going away from zero (_PyTime_ROUND_HALF_UP). */
+PyAPI_FUNC(double) _PyTime_RoundHalfUp(
+    double x);
+
 /* Convert a number of seconds, int or float, to time_t. */
 PyAPI_FUNC(int) _PyTime_ObjectToTime_t(
     PyObject *obj,
index db13b12ef3d0c4251429e3864d94c8f1f2d3a73f..d661460fa8514433410d2ab154acece2a7e894c9 100644 (file)
@@ -316,6 +316,14 @@ def _divide_and_round(a, b):
 
     return q
 
+def _round_half_up(x):
+    """Round to nearest with ties going away from zero."""
+    if x >= 0.0:
+        return _math.floor(x + 0.5)
+    else:
+        return _math.ceil(x - 0.5)
+
+
 class timedelta:
     """Represent the difference between two datetime objects.
 
@@ -399,7 +407,7 @@ class timedelta:
         # secondsfrac isn't referenced again
 
         if isinstance(microseconds, float):
-            microseconds = round(microseconds + usdouble)
+            microseconds = _round_half_up(microseconds + usdouble)
             seconds, microseconds = divmod(microseconds, 1000000)
             days, seconds = divmod(seconds, 24*3600)
             d += days
@@ -410,7 +418,7 @@ class timedelta:
             days, seconds = divmod(seconds, 24*3600)
             d += days
             s += seconds
-            microseconds = round(microseconds + usdouble)
+            microseconds = _round_half_up(microseconds + usdouble)
         assert isinstance(s, int)
         assert isinstance(microseconds, int)
         assert abs(s) <= 3 * 24 * 3600
index babeb44c06f019fd48ff79b9e01189196f75e717..62f55272d9d530f9b095e70f11612ae7f32eac10 100644 (file)
@@ -662,28 +662,24 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
         # Single-field rounding.
         eq(td(milliseconds=0.4/1000), td(0))    # rounds to 0
         eq(td(milliseconds=-0.4/1000), td(0))    # rounds to 0
-        eq(td(milliseconds=0.5/1000), td(microseconds=0))
-        eq(td(milliseconds=-0.5/1000), td(microseconds=0))
+        eq(td(milliseconds=0.5/1000), td(microseconds=1))
+        eq(td(milliseconds=-0.5/1000), td(microseconds=-1))
         eq(td(milliseconds=0.6/1000), td(microseconds=1))
         eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
-        eq(td(seconds=0.5/10**6), td(microseconds=0))
-        eq(td(seconds=-0.5/10**6), td(microseconds=0))
+        eq(td(seconds=0.5/10**6), td(microseconds=1))
+        eq(td(seconds=-0.5/10**6), td(microseconds=-1))
 
         # Rounding due to contributions from more than one field.
         us_per_hour = 3600e6
         us_per_day = us_per_hour * 24
         eq(td(days=.4/us_per_day), 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), td)
 
         eq(td(days=-.4/us_per_day), 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))
 
-        # 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 e08905ced5e04a877d8a942b318ff5d991f30d52..c9b925a001632fae2e2d35cd9a59ce2118660064 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -17,6 +17,11 @@ Core and Builtins
 Library
 -------
 
+- Issue #23517: datetime.timedelta constructor now rounds microseconds to
+  nearest with ties going away from zero (ROUND_HALF_UP), as Python 2 and
+  Python older than 3.3, instead of rounding to nearest with ties going to
+  nearest even integer (ROUND_HALF_EVEN).
+
 - Issue #23552: Timeit now warns when there is substantial (4x) variance
   between best and worst times. Patch from Serhiy Storchaka.
 
index 5cff3f8ffc1a853682635cb17d5a3b315de5aec5..6cab1e2d2421cdbe0ca7ee3159119343bdf857b3 100644 (file)
@@ -2149,29 +2149,9 @@ delta_new(PyTypeObject *type, PyObject *args, PyObject *kw)
     if (leftover_us) {
         /* Round to nearest whole # of us, and add into x. */
         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;
-        }
+        whole_us = _PyTime_RoundHalfUp(leftover_us);
 
         temp = PyLong_FromLong((long)whole_us);
 
index ffb390afe56fe4c49485551fcba834e19ad722d1..02a1edfea0612015e1875f7e88bb3af100220b9c 100644 (file)
@@ -60,8 +60,7 @@ _PyLong_FromTime_t(time_t t)
 #endif
 }
 
-/* Round to nearest with ties going away from zero (_PyTime_ROUND_HALF_UP). */
-static double
+double
 _PyTime_RoundHalfUp(double x)
 {
     /* volatile avoids optimization changing how numbers are rounded */