summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Include/pytime.h4
-rw-r--r--Lib/datetime.py12
-rw-r--r--Lib/test/datetimetester.py14
-rw-r--r--Misc/NEWS5
-rw-r--r--Modules/_datetimemodule.c22
-rw-r--r--Python/pytime.c3
6 files changed, 26 insertions, 34 deletions
diff --git a/Include/pytime.h b/Include/pytime.h
index 98ae12b..41fb806 100644
--- a/Include/pytime.h
+++ b/Include/pytime.h
@@ -44,6 +44,10 @@ PyAPI_FUNC(PyObject *) _PyLong_FromTime_t(
PyAPI_FUNC(time_t) _PyLong_AsTime_t(
PyObject *obj);
+/* Round to nearest with ties going away from zero (_PyTime_ROUND_HALF_UP). */
+PyAPI_FUNC(double) _PyTime_RoundHalfUp(
+ double x);
+
/* Convert a number of seconds, int or float, to time_t. */
PyAPI_FUNC(int) _PyTime_ObjectToTime_t(
PyObject *obj,
diff --git a/Lib/datetime.py b/Lib/datetime.py
index db13b12..d661460 100644
--- a/Lib/datetime.py
+++ b/Lib/datetime.py
@@ -316,6 +316,14 @@ def _divide_and_round(a, b):
return q
+def _round_half_up(x):
+ """Round to nearest with ties going away from zero."""
+ if x >= 0.0:
+ return _math.floor(x + 0.5)
+ else:
+ return _math.ceil(x - 0.5)
+
+
class timedelta:
"""Represent the difference between two datetime objects.
@@ -399,7 +407,7 @@ class timedelta:
# secondsfrac isn't referenced again
if isinstance(microseconds, float):
- microseconds = round(microseconds + usdouble)
+ microseconds = _round_half_up(microseconds + usdouble)
seconds, microseconds = divmod(microseconds, 1000000)
days, seconds = divmod(seconds, 24*3600)
d += days
@@ -410,7 +418,7 @@ class timedelta:
days, seconds = divmod(seconds, 24*3600)
d += days
s += seconds
- microseconds = round(microseconds + usdouble)
+ microseconds = _round_half_up(microseconds + usdouble)
assert isinstance(s, int)
assert isinstance(microseconds, int)
assert abs(s) <= 3 * 24 * 3600
diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py
index babeb44..62f5527 100644
--- a/Lib/test/datetimetester.py
+++ b/Lib/test/datetimetester.py
@@ -662,28 +662,24 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
# Single-field rounding.
eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
- eq(td(milliseconds=0.5/1000), td(microseconds=0))
- eq(td(milliseconds=-0.5/1000), td(microseconds=0))
+ eq(td(milliseconds=0.5/1000), td(microseconds=1))
+ eq(td(milliseconds=-0.5/1000), td(microseconds=-1))
eq(td(milliseconds=0.6/1000), td(microseconds=1))
eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
- eq(td(seconds=0.5/10**6), td(microseconds=0))
- eq(td(seconds=-0.5/10**6), td(microseconds=0))
+ eq(td(seconds=0.5/10**6), td(microseconds=1))
+ eq(td(seconds=-0.5/10**6), td(microseconds=-1))
# Rounding due to contributions from more than one field.
us_per_hour = 3600e6
us_per_day = us_per_hour * 24
eq(td(days=.4/us_per_day), td(0))
eq(td(hours=.2/us_per_hour), td(0))
- eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
+ eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1), td)
eq(td(days=-.4/us_per_day), td(0))
eq(td(hours=-.2/us_per_hour), td(0))
eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
- # Test for a patch in Issue 8860
- eq(td(microseconds=0.5), 0.5*td(microseconds=1.0))
- eq(td(microseconds=0.5)//td.resolution, 0.5*td.resolution//td.resolution)
-
def test_massive_normalization(self):
td = timedelta(microseconds=-1)
self.assertEqual((td.days, td.seconds, td.microseconds),
diff --git a/Misc/NEWS b/Misc/NEWS
index e08905c..c9b925a 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -17,6 +17,11 @@ Core and Builtins
Library
-------
+- 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).
+
- Issue #23552: Timeit now warns when there is substantial (4x) variance
between best and worst times. Patch from Serhiy Storchaka.
diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c
index 5cff3f8..6cab1e2 100644
--- a/Modules/_datetimemodule.c
+++ b/Modules/_datetimemodule.c
@@ -2149,29 +2149,9 @@ delta_new(PyTypeObject *type, PyObject *args, PyObject *kw)
if (leftover_us) {
/* Round to nearest whole # of us, and add into x. */
double whole_us = round(leftover_us);
- int x_is_odd;
PyObject *temp;
- whole_us = round(leftover_us);
- if (fabs(whole_us - leftover_us) == 0.5) {
- /* We're exactly halfway between two integers. In order
- * to do round-half-to-even, we must determine whether x
- * is odd. Note that x is odd when it's last bit is 1. The
- * code below uses bitwise and operation to check the last
- * bit. */
- temp = PyNumber_And(x, one); /* temp <- x & 1 */
- if (temp == NULL) {
- Py_DECREF(x);
- goto Done;
- }
- x_is_odd = PyObject_IsTrue(temp);
- Py_DECREF(temp);
- if (x_is_odd == -1) {
- Py_DECREF(x);
- goto Done;
- }
- whole_us = 2.0 * round((leftover_us + x_is_odd) * 0.5) - x_is_odd;
- }
+ whole_us = _PyTime_RoundHalfUp(leftover_us);
temp = PyLong_FromLong((long)whole_us);
diff --git a/Python/pytime.c b/Python/pytime.c
index ffb390a..02a1edf 100644
--- a/Python/pytime.c
+++ b/Python/pytime.c
@@ -60,8 +60,7 @@ _PyLong_FromTime_t(time_t t)
#endif
}
-/* Round to nearest with ties going away from zero (_PyTime_ROUND_HALF_UP). */
-static double
+double
_PyTime_RoundHalfUp(double x)
{
/* volatile avoids optimization changing how numbers are rounded */