Kaydet (Commit) 74474232 authored tarafından Victor Stinner's avatar Victor Stinner

Issue #23517: Add "half up" rounding mode to the _PyTime API

üst bbdda21a
...@@ -30,7 +30,10 @@ typedef enum { ...@@ -30,7 +30,10 @@ typedef enum {
_PyTime_ROUND_FLOOR=0, _PyTime_ROUND_FLOOR=0,
/* Round towards infinity (+inf). /* Round towards infinity (+inf).
For example, used for timeout to wait "at least" N seconds. */ For example, used for timeout to wait "at least" N seconds. */
_PyTime_ROUND_CEILING _PyTime_ROUND_CEILING=1,
/* Round to nearest with ties going away from zero.
For example, used to round from a Python float. */
_PyTime_ROUND_HALF_UP
} _PyTime_round_t; } _PyTime_round_t;
/* Convert a time_t to a PyLong. */ /* Convert a time_t to a PyLong. */
......
...@@ -30,8 +30,11 @@ class _PyTime(enum.IntEnum): ...@@ -30,8 +30,11 @@ class _PyTime(enum.IntEnum):
ROUND_FLOOR = 0 ROUND_FLOOR = 0
# Round towards infinity (+inf) # Round towards infinity (+inf)
ROUND_CEILING = 1 ROUND_CEILING = 1
# Round to nearest with ties going away from zero
ROUND_HALF_UP = 2
ALL_ROUNDING_METHODS = (_PyTime.ROUND_FLOOR, _PyTime.ROUND_CEILING) ALL_ROUNDING_METHODS = (_PyTime.ROUND_FLOOR, _PyTime.ROUND_CEILING,
_PyTime.ROUND_HALF_UP)
class TimeTestCase(unittest.TestCase): class TimeTestCase(unittest.TestCase):
...@@ -753,11 +756,11 @@ class TestPyTime_t(unittest.TestCase): ...@@ -753,11 +756,11 @@ class TestPyTime_t(unittest.TestCase):
(123.0, 123 * SEC_TO_NS), (123.0, 123 * SEC_TO_NS),
(-7.0, -7 * SEC_TO_NS), (-7.0, -7 * SEC_TO_NS),
# nanosecond are kept for value <= 2^23 seconds # nanosecond are kept for value <= 2^23 seconds,
# except 2**23-1e-9 with HALF_UP
(2**22 - 1e-9, 4194303999999999), (2**22 - 1e-9, 4194303999999999),
(2**22, 4194304000000000), (2**22, 4194304000000000),
(2**22 + 1e-9, 4194304000000001), (2**22 + 1e-9, 4194304000000001),
(2**23 - 1e-9, 8388607999999999),
(2**23, 8388608000000000), (2**23, 8388608000000000),
# start loosing precision for value > 2^23 seconds # start loosing precision for value > 2^23 seconds
...@@ -790,24 +793,36 @@ class TestPyTime_t(unittest.TestCase): ...@@ -790,24 +793,36 @@ class TestPyTime_t(unittest.TestCase):
# Conversion giving different results depending on the rounding method # Conversion giving different results depending on the rounding method
FLOOR = _PyTime.ROUND_FLOOR FLOOR = _PyTime.ROUND_FLOOR
CEILING = _PyTime.ROUND_CEILING CEILING = _PyTime.ROUND_CEILING
HALF_UP = _PyTime.ROUND_HALF_UP
for obj, ts, rnd in ( for obj, ts, rnd in (
# close to zero # close to zero
( 1e-10, 0, FLOOR), ( 1e-10, 0, FLOOR),
( 1e-10, 1, CEILING), ( 1e-10, 1, CEILING),
( 1e-10, 0, HALF_UP),
(-1e-10, -1, FLOOR), (-1e-10, -1, FLOOR),
(-1e-10, 0, CEILING), (-1e-10, 0, CEILING),
(-1e-10, 0, HALF_UP),
# test rounding of the last nanosecond # test rounding of the last nanosecond
( 1.1234567899, 1123456789, FLOOR), ( 1.1234567899, 1123456789, FLOOR),
( 1.1234567899, 1123456790, CEILING), ( 1.1234567899, 1123456790, CEILING),
( 1.1234567899, 1123456790, HALF_UP),
(-1.1234567899, -1123456790, FLOOR), (-1.1234567899, -1123456790, FLOOR),
(-1.1234567899, -1123456789, CEILING), (-1.1234567899, -1123456789, CEILING),
(-1.1234567899, -1123456790, HALF_UP),
# close to 1 second # close to 1 second
( 0.9999999999, 999999999, FLOOR), ( 0.9999999999, 999999999, FLOOR),
( 0.9999999999, 1000000000, CEILING), ( 0.9999999999, 1000000000, CEILING),
( 0.9999999999, 1000000000, HALF_UP),
(-0.9999999999, -1000000000, FLOOR), (-0.9999999999, -1000000000, FLOOR),
(-0.9999999999, -999999999, CEILING), (-0.9999999999, -999999999, CEILING),
(-0.9999999999, -1000000000, HALF_UP),
# close to 2^23 seconds
(2**23 - 1e-9, 8388607999999999, FLOOR),
(2**23 - 1e-9, 8388607999999999, CEILING),
(2**23 - 1e-9, 8388608000000000, HALF_UP),
): ):
with self.subTest(obj=obj, round=rnd, timestamp=ts): with self.subTest(obj=obj, round=rnd, timestamp=ts):
self.assertEqual(PyTime_FromSecondsObject(obj, rnd), ts) self.assertEqual(PyTime_FromSecondsObject(obj, rnd), ts)
...@@ -875,18 +890,33 @@ class TestPyTime_t(unittest.TestCase): ...@@ -875,18 +890,33 @@ class TestPyTime_t(unittest.TestCase):
FLOOR = _PyTime.ROUND_FLOOR FLOOR = _PyTime.ROUND_FLOOR
CEILING = _PyTime.ROUND_CEILING CEILING = _PyTime.ROUND_CEILING
HALF_UP = _PyTime.ROUND_HALF_UP
for ns, tv, rnd in ( for ns, tv, rnd in (
# nanoseconds # nanoseconds
(1, (0, 0), FLOOR), (1, (0, 0), FLOOR),
(1, (0, 1), CEILING), (1, (0, 1), CEILING),
(1, (0, 0), HALF_UP),
(-1, (-1, 999999), FLOOR), (-1, (-1, 999999), FLOOR),
(-1, (0, 0), CEILING), (-1, (0, 0), CEILING),
(-1, (0, 0), HALF_UP),
# seconds + nanoseconds # seconds + nanoseconds
(1234567001, (1, 234567), FLOOR), (1234567001, (1, 234567), FLOOR),
(1234567001, (1, 234568), CEILING), (1234567001, (1, 234568), CEILING),
(1234567001, (1, 234567), HALF_UP),
(-1234567001, (-2, 765432), FLOOR), (-1234567001, (-2, 765432), FLOOR),
(-1234567001, (-2, 765433), CEILING), (-1234567001, (-2, 765433), CEILING),
(-1234567001, (-2, 765433), HALF_UP),
# half up
(499, (0, 0), HALF_UP),
(500, (0, 1), HALF_UP),
(501, (0, 1), HALF_UP),
(999, (0, 1), HALF_UP),
(-499, (0, 0), HALF_UP),
(-500, (0, 0), HALF_UP),
(-501, (-1, 999999), HALF_UP),
(-999, (-1, 999999), HALF_UP),
): ):
with self.subTest(nanoseconds=ns, timeval=tv, round=rnd): with self.subTest(nanoseconds=ns, timeval=tv, round=rnd):
self.assertEqual(PyTime_AsTimeval(ns, rnd), tv) self.assertEqual(PyTime_AsTimeval(ns, rnd), tv)
...@@ -929,18 +959,33 @@ class TestPyTime_t(unittest.TestCase): ...@@ -929,18 +959,33 @@ class TestPyTime_t(unittest.TestCase):
FLOOR = _PyTime.ROUND_FLOOR FLOOR = _PyTime.ROUND_FLOOR
CEILING = _PyTime.ROUND_CEILING CEILING = _PyTime.ROUND_CEILING
HALF_UP = _PyTime.ROUND_HALF_UP
for ns, ms, rnd in ( for ns, ms, rnd in (
# nanoseconds # nanoseconds
(1, 0, FLOOR), (1, 0, FLOOR),
(1, 1, CEILING), (1, 1, CEILING),
(1, 0, HALF_UP),
(-1, 0, FLOOR), (-1, 0, FLOOR),
(-1, -1, CEILING), (-1, -1, CEILING),
(-1, 0, HALF_UP),
# seconds + nanoseconds # seconds + nanoseconds
(1234 * MS_TO_NS + 1, 1234, FLOOR), (1234 * MS_TO_NS + 1, 1234, FLOOR),
(1234 * MS_TO_NS + 1, 1235, CEILING), (1234 * MS_TO_NS + 1, 1235, CEILING),
(1234 * MS_TO_NS + 1, 1234, HALF_UP),
(-1234 * MS_TO_NS - 1, -1234, FLOOR), (-1234 * MS_TO_NS - 1, -1234, FLOOR),
(-1234 * MS_TO_NS - 1, -1235, CEILING), (-1234 * MS_TO_NS - 1, -1235, CEILING),
(-1234 * MS_TO_NS - 1, -1234, HALF_UP),
# half up
(499999, 0, HALF_UP),
(499999, 0, HALF_UP),
(500000, 1, HALF_UP),
(999999, 1, HALF_UP),
(-499999, 0, HALF_UP),
(-500000, -1, HALF_UP),
(-500001, -1, HALF_UP),
(-999999, -1, HALF_UP),
): ):
with self.subTest(nanoseconds=ns, milliseconds=ms, round=rnd): with self.subTest(nanoseconds=ns, milliseconds=ms, round=rnd):
self.assertEqual(PyTime_AsMilliseconds(ns, rnd), ms) self.assertEqual(PyTime_AsMilliseconds(ns, rnd), ms)
...@@ -966,18 +1011,31 @@ class TestPyTime_t(unittest.TestCase): ...@@ -966,18 +1011,31 @@ class TestPyTime_t(unittest.TestCase):
FLOOR = _PyTime.ROUND_FLOOR FLOOR = _PyTime.ROUND_FLOOR
CEILING = _PyTime.ROUND_CEILING CEILING = _PyTime.ROUND_CEILING
HALF_UP = _PyTime.ROUND_HALF_UP
for ns, ms, rnd in ( for ns, ms, rnd in (
# nanoseconds # nanoseconds
(1, 0, FLOOR), (1, 0, FLOOR),
(1, 1, CEILING), (1, 1, CEILING),
(1, 0, HALF_UP),
(-1, 0, FLOOR), (-1, 0, FLOOR),
(-1, -1, CEILING), (-1, -1, CEILING),
(-1, 0, HALF_UP),
# seconds + nanoseconds # seconds + nanoseconds
(1234 * US_TO_NS + 1, 1234, FLOOR), (1234 * US_TO_NS + 1, 1234, FLOOR),
(1234 * US_TO_NS + 1, 1235, CEILING), (1234 * US_TO_NS + 1, 1235, CEILING),
(1234 * US_TO_NS + 1, 1234, HALF_UP),
(-1234 * US_TO_NS - 1, -1234, FLOOR), (-1234 * US_TO_NS - 1, -1234, FLOOR),
(-1234 * US_TO_NS - 1, -1235, CEILING), (-1234 * US_TO_NS - 1, -1235, CEILING),
(-1234 * US_TO_NS - 1, -1234, HALF_UP),
# half up
(1499, 1, HALF_UP),
(1500, 2, HALF_UP),
(1501, 2, HALF_UP),
(-1499, -1, HALF_UP),
(-1500, -2, HALF_UP),
(-1501, -2, HALF_UP),
): ):
with self.subTest(nanoseconds=ns, milliseconds=ms, round=rnd): with self.subTest(nanoseconds=ns, milliseconds=ms, round=rnd):
self.assertEqual(PyTime_AsMicroseconds(ns, rnd), ms) self.assertEqual(PyTime_AsMicroseconds(ns, rnd), ms)
......
...@@ -2646,7 +2646,9 @@ run_in_subinterp(PyObject *self, PyObject *args) ...@@ -2646,7 +2646,9 @@ run_in_subinterp(PyObject *self, PyObject *args)
static int static int
check_time_rounding(int round) check_time_rounding(int round)
{ {
if (round != _PyTime_ROUND_FLOOR && round != _PyTime_ROUND_CEILING) { if (round != _PyTime_ROUND_FLOOR
&& round != _PyTime_ROUND_CEILING
&& round != _PyTime_ROUND_HALF_UP) {
PyErr_SetString(PyExc_ValueError, "invalid rounding"); PyErr_SetString(PyExc_ValueError, "invalid rounding");
return -1; return -1;
} }
......
...@@ -60,6 +60,17 @@ _PyLong_FromTime_t(time_t t) ...@@ -60,6 +60,17 @@ _PyLong_FromTime_t(time_t t)
#endif #endif
} }
static double
_PyTime_RoundHalfUp(double x)
{
if (x >= 0.0)
x = floor(x + 0.5);
else
x = ceil(x - 0.5);
return x;
}
static int static int
_PyTime_DoubleToDenominator(double d, time_t *sec, long *numerator, _PyTime_DoubleToDenominator(double d, time_t *sec, long *numerator,
double denominator, _PyTime_round_t round) double denominator, _PyTime_round_t round)
...@@ -75,7 +86,9 @@ _PyTime_DoubleToDenominator(double d, time_t *sec, long *numerator, ...@@ -75,7 +86,9 @@ _PyTime_DoubleToDenominator(double d, time_t *sec, long *numerator,
} }
floatpart *= denominator; floatpart *= denominator;
if (round == _PyTime_ROUND_CEILING) { if (round == _PyTime_ROUND_HALF_UP)
floatpart = _PyTime_RoundHalfUp(floatpart);
else if (round == _PyTime_ROUND_CEILING) {
floatpart = ceil(floatpart); floatpart = ceil(floatpart);
if (floatpart >= denominator) { if (floatpart >= denominator) {
floatpart = 0.0; floatpart = 0.0;
...@@ -124,7 +137,9 @@ _PyTime_ObjectToTime_t(PyObject *obj, time_t *sec, _PyTime_round_t round) ...@@ -124,7 +137,9 @@ _PyTime_ObjectToTime_t(PyObject *obj, time_t *sec, _PyTime_round_t round)
double d, intpart, err; double d, intpart, err;
d = PyFloat_AsDouble(obj); d = PyFloat_AsDouble(obj);
if (round == _PyTime_ROUND_CEILING) if (round == _PyTime_ROUND_HALF_UP)
d = _PyTime_RoundHalfUp(d);
else if (round == _PyTime_ROUND_CEILING)
d = ceil(d); d = ceil(d);
else else
d = floor(d); d = floor(d);
...@@ -247,7 +262,9 @@ _PyTime_FromFloatObject(_PyTime_t *t, double value, _PyTime_round_t round, ...@@ -247,7 +262,9 @@ _PyTime_FromFloatObject(_PyTime_t *t, double value, _PyTime_round_t round,
d = value; d = value;
d *= to_nanoseconds; d *= to_nanoseconds;
if (round == _PyTime_ROUND_CEILING) if (round == _PyTime_ROUND_HALF_UP)
d = _PyTime_RoundHalfUp(d);
else if (round == _PyTime_ROUND_CEILING)
d = ceil(d); d = ceil(d);
else else
d = floor(d); d = floor(d);
...@@ -333,7 +350,19 @@ static _PyTime_t ...@@ -333,7 +350,19 @@ static _PyTime_t
_PyTime_Divide(_PyTime_t t, _PyTime_t k, _PyTime_round_t round) _PyTime_Divide(_PyTime_t t, _PyTime_t k, _PyTime_round_t round)
{ {
assert(k > 1); assert(k > 1);
if (round == _PyTime_ROUND_CEILING) { if (round == _PyTime_ROUND_HALF_UP) {
_PyTime_t x, r;
x = t / k;
r = t % k;
if (Py_ABS(r) >= k / 2) {
if (t >= 0)
x++;
else
x--;
}
return x;
}
else if (round == _PyTime_ROUND_CEILING) {
if (t >= 0) if (t >= 0)
return (t + k - 1) / k; return (t + k - 1) / k;
else else
...@@ -359,8 +388,10 @@ static int ...@@ -359,8 +388,10 @@ static int
_PyTime_AsTimeval_impl(_PyTime_t t, struct timeval *tv, _PyTime_round_t round, _PyTime_AsTimeval_impl(_PyTime_t t, struct timeval *tv, _PyTime_round_t round,
int raise) int raise)
{ {
const long k = US_TO_NS;
_PyTime_t secs, ns; _PyTime_t secs, ns;
int res = 0; int res = 0;
int usec;
secs = t / SEC_TO_NS; secs = t / SEC_TO_NS;
ns = t % SEC_TO_NS; ns = t % SEC_TO_NS;
...@@ -392,20 +423,33 @@ _PyTime_AsTimeval_impl(_PyTime_t t, struct timeval *tv, _PyTime_round_t round, ...@@ -392,20 +423,33 @@ _PyTime_AsTimeval_impl(_PyTime_t t, struct timeval *tv, _PyTime_round_t round,
res = -1; res = -1;
#endif #endif
if (round == _PyTime_ROUND_CEILING) if (round == _PyTime_ROUND_HALF_UP) {
tv->tv_usec = (int)((ns + US_TO_NS - 1) / US_TO_NS); _PyTime_t r;
usec = (int)(ns / k);
r = ns % k;
if (Py_ABS(r) >= k / 2) {
if (ns >= 0)
usec++;
else
usec--;
}
}
else if (round == _PyTime_ROUND_CEILING)
usec = (int)((ns + k - 1) / k);
else else
tv->tv_usec = (int)(ns / US_TO_NS); usec = (int)(ns / k);
if (tv->tv_usec >= SEC_TO_US) { if (usec >= SEC_TO_US) {
tv->tv_usec -= SEC_TO_US; usec -= SEC_TO_US;
tv->tv_sec += 1; tv->tv_sec += 1;
} }
if (res && raise) if (res && raise)
_PyTime_overflow(); _PyTime_overflow();
assert(0 <= tv->tv_usec && tv->tv_usec <= 999999); assert(0 <= usec && usec <= 999999);
tv->tv_usec = usec;
return res; return res;
} }
......
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