[Python-checkins] cpython (merge 3.4 -> default): Fixes #23521: Corrected pure python implementation of timedelta division.

alexander.belopolsky python-checkins at python.org
Sat Feb 28 16:46:09 CET 2015


https://hg.python.org/cpython/rev/d783132d72bc
changeset:   94788:d783132d72bc
parent:      94785:eae459e35cb9
parent:      94787:8fe15bf68522
user:        Alexander Belopolsky <alexander.belopolsky at gmail.com>
date:        Sat Feb 28 10:44:47 2015 -0500
summary:
  Fixes #23521: Corrected pure python implementation of timedelta division.

 * Eliminated OverflowError from timedelta * float for some floats;
 * Corrected rounding in timedlta true division.

files:
  Lib/datetime.py            |  26 +++++++++++++++++--
  Lib/test/datetimetester.py |  34 ++++++++++++++++++++++++++
  Misc/NEWS                  |   4 +++
  3 files changed, 61 insertions(+), 3 deletions(-)


diff --git a/Lib/datetime.py b/Lib/datetime.py
--- a/Lib/datetime.py
+++ b/Lib/datetime.py
@@ -297,6 +297,25 @@
     raise TypeError("can't compare '%s' to '%s'" % (
                     type(x).__name__, type(y).__name__))
 
+def _divide_and_round(a, b):
+    """divide a by b and round result to the nearest integer
+
+    When the ratio is exactly half-way between two integers,
+    the even integer is returned.
+    """
+    # Based on the reference implementation for divmod_near
+    # in Objects/longobject.c.
+    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.
+    r *= 2
+    greater_than_half = r > b if b > 0 else r < b
+    if greater_than_half or r == b and q % 2 == 1:
+        q += 1
+
+    return q
+
 class timedelta:
     """Represent the difference between two datetime objects.
 
@@ -515,8 +534,9 @@
                              self._seconds * other,
                              self._microseconds * other)
         if isinstance(other, float):
+            usec = self._to_microseconds()
             a, b = other.as_integer_ratio()
-            return self * a / b
+            return timedelta(0, 0, _divide_and_round(usec * a, b))
         return NotImplemented
 
     __rmul__ = __mul__
@@ -541,10 +561,10 @@
         if isinstance(other, timedelta):
             return usec / other._to_microseconds()
         if isinstance(other, int):
-            return timedelta(0, 0, usec / other)
+            return timedelta(0, 0, _divide_and_round(usec, other))
         if isinstance(other, float):
             a, b = other.as_integer_ratio()
-            return timedelta(0, 0, b * usec / a)
+            return timedelta(0, 0, _divide_and_round(b * usec, a))
 
     def __mod__(self, other):
         if isinstance(other, timedelta):
diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py
--- a/Lib/test/datetimetester.py
+++ b/Lib/test/datetimetester.py
@@ -62,6 +62,33 @@
                        'tzinfo'])
         self.assertEqual(names - allowed, set([]))
 
+    def test_divide_and_round(self):
+        if '_Fast' in str(self):
+            return
+        dar = datetime_module._divide_and_round
+
+        self.assertEqual(dar(-10, -3), 3)
+        self.assertEqual(dar(5, -2), -2)
+
+        # four cases: (2 signs of a) x (2 signs of b)
+        self.assertEqual(dar(7, 3), 2)
+        self.assertEqual(dar(-7, 3), -2)
+        self.assertEqual(dar(7, -3), -2)
+        self.assertEqual(dar(-7, -3), 2)
+
+        # ties to even - eight cases:
+        # (2 signs of a) x (2 signs of b) x (even / odd quotient)
+        self.assertEqual(dar(10, 4), 2)
+        self.assertEqual(dar(-10, 4), -2)
+        self.assertEqual(dar(10, -4), -2)
+        self.assertEqual(dar(-10, -4), 2)
+
+        self.assertEqual(dar(6, 4), 2)
+        self.assertEqual(dar(-6, 4), -2)
+        self.assertEqual(dar(6, -4), -2)
+        self.assertEqual(dar(-6, -4), 2)
+
+
 #############################################################################
 # tzinfo tests
 
@@ -394,6 +421,10 @@
         eq((-3*us) * 0.5, -2*us)
         eq((-5*us) * 0.5, -2*us)
 
+        # Issue #23521
+        eq(td(seconds=1) * 0.123456, td(microseconds=123456))
+        eq(td(seconds=1) * 0.6112295, td(microseconds=611229))
+
         # Division by int and float
         eq((3*us) / 2, 2*us)
         eq((5*us) / 2, 2*us)
@@ -408,6 +439,9 @@
         for i in range(-10, 10):
             eq((i*us/-3)//us, round(i/-3))
 
+        # Issue #23521
+        eq(td(seconds=1) / (1 / 0.6112295), td(microseconds=611229))
+
         # Issue #11576
         eq(td(999999999, 86399, 999999) - td(999999999, 86399, 999998),
            td(0, 0, 1))
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -12,6 +12,10 @@
 
 Library
 -------
+- Issue #23521: Corrected pure python implementation of timedelta division.
+
+ * Eliminated OverflowError from timedelta * float for some floats;
+ * Corrected rounding in timedlta true division.
 
 - Issue #21619: Popen objects no longer leave a zombie after exit in the with
   statement if the pipe was broken.  Patch by Martin Panter.

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list