From 24d3deefcf2f78b12fc29ed4c9a3811d46c534bb Mon Sep 17 00:00:00 2001 From: Alexander Belopolsky Date: Sat, 28 Feb 2015 10:41:57 -0500 Subject: Fixes #23521: Corrected pure python implementation of timedelta division. * Eliminated OverflowError from timedelta * float for some floats; * Corrected rounding in timedlta true division. --- 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 index 1789714..4c442f2 100644 --- a/Lib/datetime.py +++ b/Lib/datetime.py @@ -280,6 +280,25 @@ def _cmperror(x, y): 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. @@ -506,8 +525,9 @@ class timedelta: 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__ @@ -532,10 +552,10 @@ class timedelta: 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 index f42b0f9..8e48b9f 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -50,6 +50,33 @@ class TestModule(unittest.TestCase): self.assertEqual(datetime.MINYEAR, 1) self.assertEqual(datetime.MAXYEAR, 9999) + 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 @@ -382,6 +409,10 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase): 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) @@ -396,6 +427,9 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase): 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 index 05fde70..c3f5154 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -12,6 +12,10 @@ Core and Builtins 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. -- cgit v0.12