Commit 24d3deef authored by Alexander Belopolsky's avatar Alexander Belopolsky

Fixes #23521: Corrected pure python implementation of timedelta division.

 * Eliminated OverflowError from timedelta * float for some floats;
 * Corrected rounding in timedlta true division.
parent cf265fd0
...@@ -280,6 +280,25 @@ def _cmperror(x, y): ...@@ -280,6 +280,25 @@ def _cmperror(x, y):
raise TypeError("can't compare '%s' to '%s'" % ( raise TypeError("can't compare '%s' to '%s'" % (
type(x).__name__, type(y).__name__)) 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: class timedelta:
"""Represent the difference between two datetime objects. """Represent the difference between two datetime objects.
...@@ -506,8 +525,9 @@ class timedelta: ...@@ -506,8 +525,9 @@ class timedelta:
self._seconds * other, self._seconds * other,
self._microseconds * other) self._microseconds * other)
if isinstance(other, float): if isinstance(other, float):
usec = self._to_microseconds()
a, b = other.as_integer_ratio() a, b = other.as_integer_ratio()
return self * a / b return timedelta(0, 0, _divide_and_round(usec * a, b))
return NotImplemented return NotImplemented
__rmul__ = __mul__ __rmul__ = __mul__
...@@ -532,10 +552,10 @@ class timedelta: ...@@ -532,10 +552,10 @@ class timedelta:
if isinstance(other, timedelta): if isinstance(other, timedelta):
return usec / other._to_microseconds() return usec / other._to_microseconds()
if isinstance(other, int): if isinstance(other, int):
return timedelta(0, 0, usec / other) return timedelta(0, 0, _divide_and_round(usec, other))
if isinstance(other, float): if isinstance(other, float):
a, b = other.as_integer_ratio() 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): def __mod__(self, other):
if isinstance(other, timedelta): if isinstance(other, timedelta):
......
...@@ -50,6 +50,33 @@ class TestModule(unittest.TestCase): ...@@ -50,6 +50,33 @@ class TestModule(unittest.TestCase):
self.assertEqual(datetime.MINYEAR, 1) self.assertEqual(datetime.MINYEAR, 1)
self.assertEqual(datetime.MAXYEAR, 9999) 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 # tzinfo tests
...@@ -382,6 +409,10 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase): ...@@ -382,6 +409,10 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
eq((-3*us) * 0.5, -2*us) eq((-3*us) * 0.5, -2*us)
eq((-5*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 # Division by int and float
eq((3*us) / 2, 2*us) eq((3*us) / 2, 2*us)
eq((5*us) / 2, 2*us) eq((5*us) / 2, 2*us)
...@@ -396,6 +427,9 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase): ...@@ -396,6 +427,9 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
for i in range(-10, 10): for i in range(-10, 10):
eq((i*us/-3)//us, round(i/-3)) eq((i*us/-3)//us, round(i/-3))
# Issue #23521
eq(td(seconds=1) / (1 / 0.6112295), td(microseconds=611229))
# Issue #11576 # Issue #11576
eq(td(999999999, 86399, 999999) - td(999999999, 86399, 999998), eq(td(999999999, 86399, 999999) - td(999999999, 86399, 999998),
td(0, 0, 1)) td(0, 0, 1))
......
...@@ -12,6 +12,10 @@ Core and Builtins ...@@ -12,6 +12,10 @@ Core and Builtins
Library 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 - Issue #21619: Popen objects no longer leave a zombie after exit in the with
statement if the pipe was broken. Patch by Martin Panter. statement if the pipe was broken. Patch by Martin Panter.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment