From 62d1c70eff9498446d2ce3c564fefee7a7e54770 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 1 Apr 2015 17:47:07 +0200 Subject: [PATCH] Issue #22117, issue #23485: Fix _PyTime_AsMilliseconds() and _PyTime_AsMicroseconds() rounding. Add also unit tests. --- Lib/test/test_time.py | 76 ++++++++++++++++++++++++++++++++++++--- Modules/_testcapimodule.c | 38 ++++++++++++++++++++ Python/pytime.c | 26 +++++++------- 3 files changed, 123 insertions(+), 17 deletions(-) diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index 4747cc6ea0d..a9f6fd87edb 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -21,6 +21,8 @@ SIZEOF_INT = sysconfig.get_config_var('SIZEOF_INT') or 4 TIME_MAXYEAR = (1 << 8 * SIZEOF_INT - 1) - 1 TIME_MINYEAR = -TIME_MAXYEAR - 1 +US_TO_NS = 10 ** 3 +MS_TO_NS = 10 ** 6 SEC_TO_NS = 10 ** 9 class _PyTime(enum.IntEnum): @@ -867,10 +869,6 @@ class TestPyTime_t(unittest.TestCase): # seconds (2 * SEC_TO_NS, (2, 0)), (-3 * SEC_TO_NS, (-3, 0)), - - # seconds + nanoseconds - (1234567000, (1, 234567)), - (-1234567000, (-2, 765433)), ): with self.subTest(nanoseconds=ns, timeval=tv, round=rnd): self.assertEqual(PyTime_AsTimeval(ns, rnd), tv) @@ -914,6 +912,76 @@ class TestPyTime_t(unittest.TestCase): with self.subTest(nanoseconds=ns, timespec=ts): self.assertEqual(PyTime_AsTimespec(ns), ts) + def test_milliseconds(self): + from _testcapi import PyTime_AsMilliseconds + for rnd in ALL_ROUNDING_METHODS: + for ns, tv in ( + # milliseconds + (1 * MS_TO_NS, 1), + (-2 * MS_TO_NS, -2), + + # seconds + (2 * SEC_TO_NS, 2000), + (-3 * SEC_TO_NS, -3000), + ): + with self.subTest(nanoseconds=ns, timeval=tv, round=rnd): + self.assertEqual(PyTime_AsMilliseconds(ns, rnd), tv) + + FLOOR = _PyTime.ROUND_FLOOR + CEILING = _PyTime.ROUND_CEILING + for ns, ms, rnd in ( + # nanoseconds + (1, 0, FLOOR), + (1, 1, CEILING), + (-1, 0, FLOOR), + (-1, -1, CEILING), + + # seconds + nanoseconds + (1234 * MS_TO_NS + 1, 1234, FLOOR), + (1234 * MS_TO_NS + 1, 1235, CEILING), + (-1234 * MS_TO_NS - 1, -1234, FLOOR), + (-1234 * MS_TO_NS - 1, -1235, CEILING), + ): + with self.subTest(nanoseconds=ns, milliseconds=ms, round=rnd): + self.assertEqual(PyTime_AsMilliseconds(ns, rnd), ms) + + def test_microseconds(self): + from _testcapi import PyTime_AsMicroseconds + for rnd in ALL_ROUNDING_METHODS: + for ns, tv in ( + # microseconds + (1 * US_TO_NS, 1), + (-2 * US_TO_NS, -2), + + # milliseconds + (1 * MS_TO_NS, 1000), + (-2 * MS_TO_NS, -2000), + + # seconds + (2 * SEC_TO_NS, 2000000), + (-3 * SEC_TO_NS, -3000000), + ): + with self.subTest(nanoseconds=ns, timeval=tv, round=rnd): + self.assertEqual(PyTime_AsMicroseconds(ns, rnd), tv) + + FLOOR = _PyTime.ROUND_FLOOR + CEILING = _PyTime.ROUND_CEILING + for ns, ms, rnd in ( + # nanoseconds + (1, 0, FLOOR), + (1, 1, CEILING), + (-1, 0, FLOOR), + (-1, -1, CEILING), + + # seconds + nanoseconds + (1234 * US_TO_NS + 1, 1234, FLOOR), + (1234 * US_TO_NS + 1, 1235, CEILING), + (-1234 * US_TO_NS - 1, -1234, FLOOR), + (-1234 * US_TO_NS - 1, -1235, CEILING), + ): + with self.subTest(nanoseconds=ns, milliseconds=ms, round=rnd): + self.assertEqual(PyTime_AsMicroseconds(ns, rnd), ms) + if __name__ == "__main__": unittest.main() diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 253efb64366..7b4f2396378 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -3452,6 +3452,42 @@ test_PyTime_AsTimespec(PyObject *self, PyObject *args) } #endif +static PyObject * +test_PyTime_AsMilliseconds(PyObject *self, PyObject *args) +{ + PY_LONG_LONG ns; + int round; + _PyTime_t t, ms; + + if (!PyArg_ParseTuple(args, "Li", &ns, &round)) + return NULL; + if (check_time_rounding(round) < 0) + return NULL; + t = _PyTime_FromNanoseconds(ns); + ms = _PyTime_AsMilliseconds(t, round); + /* This conversion rely on the fact that _PyTime_t is a number of + nanoseconds */ + return _PyTime_AsNanosecondsObject(ms); +} + +static PyObject * +test_PyTime_AsMicroseconds(PyObject *self, PyObject *args) +{ + PY_LONG_LONG ns; + int round; + _PyTime_t t, ms; + + if (!PyArg_ParseTuple(args, "Li", &ns, &round)) + return NULL; + if (check_time_rounding(round) < 0) + return NULL; + t = _PyTime_FromNanoseconds(ns); + ms = _PyTime_AsMicroseconds(t, round); + /* This conversion rely on the fact that _PyTime_t is a number of + nanoseconds */ + return _PyTime_AsNanosecondsObject(ms); +} + static PyMethodDef TestMethods[] = { {"raise_exception", raise_exception, METH_VARARGS}, @@ -3621,6 +3657,8 @@ static PyMethodDef TestMethods[] = { #ifdef HAVE_CLOCK_GETTIME {"PyTime_AsTimespec", test_PyTime_AsTimespec, METH_VARARGS}, #endif + {"PyTime_AsMilliseconds", test_PyTime_AsMilliseconds, METH_VARARGS}, + {"PyTime_AsMicroseconds", test_PyTime_AsMicroseconds, METH_VARARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/Python/pytime.c b/Python/pytime.c index 003003bdcab..491bbea6112 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -19,6 +19,10 @@ #define MS_TO_NS (MS_TO_US * US_TO_NS) #define SEC_TO_NS (SEC_TO_MS * MS_TO_NS) +/* Conversion from nanoseconds */ +#define NS_TO_MS (1000 * 1000) +#define NS_TO_US (1000) + static void error_time_t_overflow(void) { @@ -288,33 +292,29 @@ _PyTime_AsNanosecondsObject(_PyTime_t t) } static _PyTime_t -_PyTime_Multiply(_PyTime_t t, unsigned int multiply, _PyTime_round_t round) +_PyTime_Divide(_PyTime_t t, _PyTime_t k, _PyTime_round_t round) { - _PyTime_t k; - if (multiply < SEC_TO_NS) { - k = SEC_TO_NS / multiply; - if (round == _PyTime_ROUND_CEILING) + assert(k > 1); + if (round == _PyTime_ROUND_CEILING) { + if (t >= 0) return (t + k - 1) / k; else - return t / k; - } - else { - k = multiply / SEC_TO_NS; - return t * k; + return (t - (k - 1)) / k; } + else + return t / k; } _PyTime_t _PyTime_AsMilliseconds(_PyTime_t t, _PyTime_round_t round) { - return _PyTime_Multiply(t, 1000, round); + return _PyTime_Divide(t, NS_TO_MS, round); } -/* FIXME: write unit tests */ _PyTime_t _PyTime_AsMicroseconds(_PyTime_t t, _PyTime_round_t round) { - return _PyTime_Multiply(t, 1000 * 1000, round); + return _PyTime_Divide(t, NS_TO_US, round); } static int