Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2ec5587

Browse files
committed
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).
1 parent 8cbb013 commit 2ec5587

6 files changed

Lines changed: 26 additions & 34 deletions

File tree

Include/pytime.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ PyAPI_FUNC(PyObject *) _PyLong_FromTime_t(
4444
PyAPI_FUNC(time_t) _PyLong_AsTime_t(
4545
PyObject *obj);
4646

47+
/* Round to nearest with ties going away from zero (_PyTime_ROUND_HALF_UP). */
48+
PyAPI_FUNC(double) _PyTime_RoundHalfUp(
49+
double x);
50+
4751
/* Convert a number of seconds, int or float, to time_t. */
4852
PyAPI_FUNC(int) _PyTime_ObjectToTime_t(
4953
PyObject *obj,

Lib/datetime.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,14 @@ def _divide_and_round(a, b):
316316

317317
return q
318318

319+
def _round_half_up(x):
320+
"""Round to nearest with ties going away from zero."""
321+
if x >= 0.0:
322+
return _math.floor(x + 0.5)
323+
else:
324+
return _math.ceil(x - 0.5)
325+
326+
319327
class timedelta:
320328
"""Represent the difference between two datetime objects.
321329
@@ -399,7 +407,7 @@ def __new__(cls, days=0, seconds=0, microseconds=0,
399407
# secondsfrac isn't referenced again
400408

401409
if isinstance(microseconds, float):
402-
microseconds = round(microseconds + usdouble)
410+
microseconds = _round_half_up(microseconds + usdouble)
403411
seconds, microseconds = divmod(microseconds, 1000000)
404412
days, seconds = divmod(seconds, 24*3600)
405413
d += days
@@ -410,7 +418,7 @@ def __new__(cls, days=0, seconds=0, microseconds=0,
410418
days, seconds = divmod(seconds, 24*3600)
411419
d += days
412420
s += seconds
413-
microseconds = round(microseconds + usdouble)
421+
microseconds = _round_half_up(microseconds + usdouble)
414422
assert isinstance(s, int)
415423
assert isinstance(microseconds, int)
416424
assert abs(s) <= 3 * 24 * 3600

Lib/test/datetimetester.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -662,28 +662,24 @@ def test_microsecond_rounding(self):
662662
# Single-field rounding.
663663
eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
664664
eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
665-
eq(td(milliseconds=0.5/1000), td(microseconds=0))
666-
eq(td(milliseconds=-0.5/1000), td(microseconds=0))
665+
eq(td(milliseconds=0.5/1000), td(microseconds=1))
666+
eq(td(milliseconds=-0.5/1000), td(microseconds=-1))
667667
eq(td(milliseconds=0.6/1000), td(microseconds=1))
668668
eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
669-
eq(td(seconds=0.5/10**6), td(microseconds=0))
670-
eq(td(seconds=-0.5/10**6), td(microseconds=0))
669+
eq(td(seconds=0.5/10**6), td(microseconds=1))
670+
eq(td(seconds=-0.5/10**6), td(microseconds=-1))
671671

672672
# Rounding due to contributions from more than one field.
673673
us_per_hour = 3600e6
674674
us_per_day = us_per_hour * 24
675675
eq(td(days=.4/us_per_day), td(0))
676676
eq(td(hours=.2/us_per_hour), td(0))
677-
eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
677+
eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1), td)
678678

679679
eq(td(days=-.4/us_per_day), td(0))
680680
eq(td(hours=-.2/us_per_hour), td(0))
681681
eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
682682

683-
# Test for a patch in Issue 8860
684-
eq(td(microseconds=0.5), 0.5*td(microseconds=1.0))
685-
eq(td(microseconds=0.5)//td.resolution, 0.5*td.resolution//td.resolution)
686-
687683
def test_massive_normalization(self):
688684
td = timedelta(microseconds=-1)
689685
self.assertEqual((td.days, td.seconds, td.microseconds),

Misc/NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ Core and Builtins
1717
Library
1818
-------
1919

20+
- Issue #23517: datetime.timedelta constructor now rounds microseconds to
21+
nearest with ties going away from zero (ROUND_HALF_UP), as Python 2 and
22+
Python older than 3.3, instead of rounding to nearest with ties going to
23+
nearest even integer (ROUND_HALF_EVEN).
24+
2025
- Issue #23552: Timeit now warns when there is substantial (4x) variance
2126
between best and worst times. Patch from Serhiy Storchaka.
2227

Modules/_datetimemodule.c

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2149,29 +2149,9 @@ delta_new(PyTypeObject *type, PyObject *args, PyObject *kw)
21492149
if (leftover_us) {
21502150
/* Round to nearest whole # of us, and add into x. */
21512151
double whole_us = round(leftover_us);
2152-
int x_is_odd;
21532152
PyObject *temp;
21542153

2155-
whole_us = round(leftover_us);
2156-
if (fabs(whole_us - leftover_us) == 0.5) {
2157-
/* We're exactly halfway between two integers. In order
2158-
* to do round-half-to-even, we must determine whether x
2159-
* is odd. Note that x is odd when it's last bit is 1. The
2160-
* code below uses bitwise and operation to check the last
2161-
* bit. */
2162-
temp = PyNumber_And(x, one); /* temp <- x & 1 */
2163-
if (temp == NULL) {
2164-
Py_DECREF(x);
2165-
goto Done;
2166-
}
2167-
x_is_odd = PyObject_IsTrue(temp);
2168-
Py_DECREF(temp);
2169-
if (x_is_odd == -1) {
2170-
Py_DECREF(x);
2171-
goto Done;
2172-
}
2173-
whole_us = 2.0 * round((leftover_us + x_is_odd) * 0.5) - x_is_odd;
2174-
}
2154+
whole_us = _PyTime_RoundHalfUp(leftover_us);
21752155

21762156
temp = PyLong_FromLong((long)whole_us);
21772157

Python/pytime.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,7 @@ _PyLong_FromTime_t(time_t t)
6060
#endif
6161
}
6262

63-
/* Round to nearest with ties going away from zero (_PyTime_ROUND_HALF_UP). */
64-
static double
63+
double
6564
_PyTime_RoundHalfUp(double x)
6665
{
6766
/* volatile avoids optimization changing how numbers are rounded */

0 commit comments

Comments
 (0)