Skip to content

Commit 184291a

Browse files
committed
Fixes #23521: Corrected pure python implementation of timedelta division.
* Eliminated OverflowError from timedelta * float for some floats; * Corrected rounding in timedlta true division.
2 parents 6ab0ec9 + 24d3dee commit 184291a

3 files changed

Lines changed: 61 additions & 3 deletions

File tree

Lib/datetime.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,25 @@ def _cmperror(x, y):
297297
raise TypeError("can't compare '%s' to '%s'" % (
298298
type(x).__name__, type(y).__name__))
299299

300+
def _divide_and_round(a, b):
301+
"""divide a by b and round result to the nearest integer
302+
303+
When the ratio is exactly half-way between two integers,
304+
the even integer is returned.
305+
"""
306+
# Based on the reference implementation for divmod_near
307+
# in Objects/longobject.c.
308+
q, r = divmod(a, b)
309+
# round up if either r / b > 0.5, or r / b == 0.5 and q is odd.
310+
# The expression r / b > 0.5 is equivalent to 2 * r > b if b is
311+
# positive, 2 * r < b if b negative.
312+
r *= 2
313+
greater_than_half = r > b if b > 0 else r < b
314+
if greater_than_half or r == b and q % 2 == 1:
315+
q += 1
316+
317+
return q
318+
300319
class timedelta:
301320
"""Represent the difference between two datetime objects.
302321
@@ -515,8 +534,9 @@ def __mul__(self, other):
515534
self._seconds * other,
516535
self._microseconds * other)
517536
if isinstance(other, float):
537+
usec = self._to_microseconds()
518538
a, b = other.as_integer_ratio()
519-
return self * a / b
539+
return timedelta(0, 0, _divide_and_round(usec * a, b))
520540
return NotImplemented
521541

522542
__rmul__ = __mul__
@@ -541,10 +561,10 @@ def __truediv__(self, other):
541561
if isinstance(other, timedelta):
542562
return usec / other._to_microseconds()
543563
if isinstance(other, int):
544-
return timedelta(0, 0, usec / other)
564+
return timedelta(0, 0, _divide_and_round(usec, other))
545565
if isinstance(other, float):
546566
a, b = other.as_integer_ratio()
547-
return timedelta(0, 0, b * usec / a)
567+
return timedelta(0, 0, _divide_and_round(b * usec, a))
548568

549569
def __mod__(self, other):
550570
if isinstance(other, timedelta):

Lib/test/datetimetester.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,33 @@ def test_name_cleanup(self):
6262
'tzinfo'])
6363
self.assertEqual(names - allowed, set([]))
6464

65+
def test_divide_and_round(self):
66+
if '_Fast' in str(self):
67+
return
68+
dar = datetime_module._divide_and_round
69+
70+
self.assertEqual(dar(-10, -3), 3)
71+
self.assertEqual(dar(5, -2), -2)
72+
73+
# four cases: (2 signs of a) x (2 signs of b)
74+
self.assertEqual(dar(7, 3), 2)
75+
self.assertEqual(dar(-7, 3), -2)
76+
self.assertEqual(dar(7, -3), -2)
77+
self.assertEqual(dar(-7, -3), 2)
78+
79+
# ties to even - eight cases:
80+
# (2 signs of a) x (2 signs of b) x (even / odd quotient)
81+
self.assertEqual(dar(10, 4), 2)
82+
self.assertEqual(dar(-10, 4), -2)
83+
self.assertEqual(dar(10, -4), -2)
84+
self.assertEqual(dar(-10, -4), 2)
85+
86+
self.assertEqual(dar(6, 4), 2)
87+
self.assertEqual(dar(-6, 4), -2)
88+
self.assertEqual(dar(6, -4), -2)
89+
self.assertEqual(dar(-6, -4), 2)
90+
91+
6592
#############################################################################
6693
# tzinfo tests
6794

@@ -394,6 +421,10 @@ def test_computations(self):
394421
eq((-3*us) * 0.5, -2*us)
395422
eq((-5*us) * 0.5, -2*us)
396423

424+
# Issue #23521
425+
eq(td(seconds=1) * 0.123456, td(microseconds=123456))
426+
eq(td(seconds=1) * 0.6112295, td(microseconds=611229))
427+
397428
# Division by int and float
398429
eq((3*us) / 2, 2*us)
399430
eq((5*us) / 2, 2*us)
@@ -408,6 +439,9 @@ def test_computations(self):
408439
for i in range(-10, 10):
409440
eq((i*us/-3)//us, round(i/-3))
410441

442+
# Issue #23521
443+
eq(td(seconds=1) / (1 / 0.6112295), td(microseconds=611229))
444+
411445
# Issue #11576
412446
eq(td(999999999, 86399, 999999) - td(999999999, 86399, 999998),
413447
td(0, 0, 1))

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ Core and Builtins
1212

1313
Library
1414
-------
15+
- Issue #23521: Corrected pure python implementation of timedelta division.
16+
17+
* Eliminated OverflowError from timedelta * float for some floats;
18+
* Corrected rounding in timedlta true division.
1519

1620
- Issue #21619: Popen objects no longer leave a zombie after exit in the with
1721
statement if the pipe was broken. Patch by Martin Panter.

0 commit comments

Comments
 (0)