mirror of
https://github.com/python/cpython.git
synced 2025-07-23 11:15:24 +00:00
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).
This commit is contained in:
parent
8cbb013553
commit
2ec558739e
6 changed files with 26 additions and 34 deletions
|
@ -44,6 +44,10 @@ PyAPI_FUNC(PyObject *) _PyLong_FromTime_t(
|
||||||
PyAPI_FUNC(time_t) _PyLong_AsTime_t(
|
PyAPI_FUNC(time_t) _PyLong_AsTime_t(
|
||||||
PyObject *obj);
|
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. */
|
/* Convert a number of seconds, int or float, to time_t. */
|
||||||
PyAPI_FUNC(int) _PyTime_ObjectToTime_t(
|
PyAPI_FUNC(int) _PyTime_ObjectToTime_t(
|
||||||
PyObject *obj,
|
PyObject *obj,
|
||||||
|
|
|
@ -316,6 +316,14 @@ def _divide_and_round(a, b):
|
||||||
|
|
||||||
return q
|
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:
|
class timedelta:
|
||||||
"""Represent the difference between two datetime objects.
|
"""Represent the difference between two datetime objects.
|
||||||
|
|
||||||
|
@ -399,7 +407,7 @@ class timedelta:
|
||||||
# secondsfrac isn't referenced again
|
# secondsfrac isn't referenced again
|
||||||
|
|
||||||
if isinstance(microseconds, float):
|
if isinstance(microseconds, float):
|
||||||
microseconds = round(microseconds + usdouble)
|
microseconds = _round_half_up(microseconds + usdouble)
|
||||||
seconds, microseconds = divmod(microseconds, 1000000)
|
seconds, microseconds = divmod(microseconds, 1000000)
|
||||||
days, seconds = divmod(seconds, 24*3600)
|
days, seconds = divmod(seconds, 24*3600)
|
||||||
d += days
|
d += days
|
||||||
|
@ -410,7 +418,7 @@ class timedelta:
|
||||||
days, seconds = divmod(seconds, 24*3600)
|
days, seconds = divmod(seconds, 24*3600)
|
||||||
d += days
|
d += days
|
||||||
s += seconds
|
s += seconds
|
||||||
microseconds = round(microseconds + usdouble)
|
microseconds = _round_half_up(microseconds + usdouble)
|
||||||
assert isinstance(s, int)
|
assert isinstance(s, int)
|
||||||
assert isinstance(microseconds, int)
|
assert isinstance(microseconds, int)
|
||||||
assert abs(s) <= 3 * 24 * 3600
|
assert abs(s) <= 3 * 24 * 3600
|
||||||
|
|
|
@ -662,28 +662,24 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
|
||||||
# Single-field rounding.
|
# 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.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=1))
|
||||||
eq(td(milliseconds=-0.5/1000), td(microseconds=0))
|
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(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=1))
|
||||||
eq(td(seconds=-0.5/10**6), td(microseconds=0))
|
eq(td(seconds=-0.5/10**6), td(microseconds=-1))
|
||||||
|
|
||||||
# Rounding due to contributions from more than one field.
|
# Rounding due to contributions from more than one field.
|
||||||
us_per_hour = 3600e6
|
us_per_hour = 3600e6
|
||||||
us_per_day = us_per_hour * 24
|
us_per_day = us_per_hour * 24
|
||||||
eq(td(days=.4/us_per_day), td(0))
|
eq(td(days=.4/us_per_day), td(0))
|
||||||
eq(td(hours=.2/us_per_hour), 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(days=-.4/us_per_day), td(0))
|
||||||
eq(td(hours=-.2/us_per_hour), 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))
|
||||||
|
|
||||||
# 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):
|
def test_massive_normalization(self):
|
||||||
td = timedelta(microseconds=-1)
|
td = timedelta(microseconds=-1)
|
||||||
self.assertEqual((td.days, td.seconds, td.microseconds),
|
self.assertEqual((td.days, td.seconds, td.microseconds),
|
||||||
|
|
|
@ -17,6 +17,11 @@ Core and Builtins
|
||||||
Library
|
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
|
- Issue #23552: Timeit now warns when there is substantial (4x) variance
|
||||||
between best and worst times. Patch from Serhiy Storchaka.
|
between best and worst times. Patch from Serhiy Storchaka.
|
||||||
|
|
||||||
|
|
|
@ -2149,29 +2149,9 @@ delta_new(PyTypeObject *type, PyObject *args, PyObject *kw)
|
||||||
if (leftover_us) {
|
if (leftover_us) {
|
||||||
/* Round to nearest whole # of us, and add into x. */
|
/* Round to nearest whole # of us, and add into x. */
|
||||||
double whole_us = round(leftover_us);
|
double whole_us = round(leftover_us);
|
||||||
int x_is_odd;
|
|
||||||
PyObject *temp;
|
PyObject *temp;
|
||||||
|
|
||||||
whole_us = round(leftover_us);
|
whole_us = _PyTime_RoundHalfUp(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
temp = PyLong_FromLong((long)whole_us);
|
temp = PyLong_FromLong((long)whole_us);
|
||||||
|
|
||||||
|
|
|
@ -60,8 +60,7 @@ _PyLong_FromTime_t(time_t t)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Round to nearest with ties going away from zero (_PyTime_ROUND_HALF_UP). */
|
double
|
||||||
static double
|
|
||||||
_PyTime_RoundHalfUp(double x)
|
_PyTime_RoundHalfUp(double x)
|
||||||
{
|
{
|
||||||
/* volatile avoids optimization changing how numbers are rounded */
|
/* volatile avoids optimization changing how numbers are rounded */
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue