changeset: 97681:0ad37807dd4c branch: 3.4 tag: tip user: Victor Stinner date: Sat Sep 05 00:13:47 2015 +0200 files: Include/pytime.h Lib/test/test_time.py Modules/_testcapimodule.c Python/pytime.c description: test2 diff -r d4e1cc403175 -r 0ad37807dd4c Include/pytime.h --- a/Include/pytime.h Sat Sep 05 00:10:05 2015 +0200 +++ b/Include/pytime.h Sat Sep 05 00:13:47 2015 +0200 @@ -58,7 +58,10 @@ typedef enum { /* Round towards zero. */ _PyTime_ROUND_DOWN=0, /* Round away from zero. */ - _PyTime_ROUND_UP + _PyTime_ROUND_UP, + /* 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; /* Round to nearest with ties going away from zero (_PyTime_ROUND_HALF_UP). */ diff -r d4e1cc403175 -r 0ad37807dd4c Lib/test/test_time.py --- a/Lib/test/test_time.py Sat Sep 05 00:10:05 2015 +0200 +++ b/Lib/test/test_time.py Sat Sep 05 00:13:47 2015 +0200 @@ -16,6 +16,10 @@ TIME_MAXYEAR = (1 << 8 * SIZEOF_INT - 1) TIME_MINYEAR = -TIME_MAXYEAR - 1 _PyTime_ROUND_DOWN = 0 _PyTime_ROUND_UP = 1 +_PyTime_ROUND_HALF_UP = 2 + +ALL_ROUNDING_METHODS = (_PyTime_ROUND_DOWN, _PyTime_ROUND_UP, + _PyTime_ROUND_HALF_UP) class TimeTestCase(unittest.TestCase): @@ -594,21 +598,40 @@ class TestPytime(unittest.TestCase): @support.cpython_only def test_time_t(self): from _testcapi import pytime_object_to_time_t + + # Conversion giving the same result for all rounding methods + for rnd in ALL_ROUNDING_METHODS: + for obj, time_t in ( + # int + (0, 0), + (-1, -1), + + # float + (1.0, 1), + (-1.0, -1), + ): + self.assertEqual(pytime_object_to_time_t(obj, rnd), time_t) + + DOWN = _PyTime_ROUND_DOWN + UP = _PyTime_ROUND_UP + HALF_UP = _PyTime_ROUND_HALF_UP for obj, time_t, rnd in ( - # Round towards zero - (0, 0, _PyTime_ROUND_DOWN), - (-1, -1, _PyTime_ROUND_DOWN), - (-1.0, -1, _PyTime_ROUND_DOWN), - (-1.9, -1, _PyTime_ROUND_DOWN), - (1.0, 1, _PyTime_ROUND_DOWN), - (1.9, 1, _PyTime_ROUND_DOWN), - # Round away from zero - (0, 0, _PyTime_ROUND_UP), - (-1, -1, _PyTime_ROUND_UP), - (-1.0, -1, _PyTime_ROUND_UP), - (-1.9, -2, _PyTime_ROUND_UP), - (1.0, 1, _PyTime_ROUND_UP), - (1.9, 2, _PyTime_ROUND_UP), + (-1.9, -1, DOWN), + (-1.9, -2, UP), + (-1.9, -2, HALF_UP), + + (1.9, 1, DOWN), + (1.9, 2, UP), + (1.9, 2, HALF_UP), + + # half up + (-0.6, -1, HALF_UP), + (-0.5, -1, HALF_UP), + (-0.4, 0, HALF_UP), + + (0.4, 0, HALF_UP), + (0.5, 1, HALF_UP), + (0.6, 1, HALF_UP), ): self.assertEqual(pytime_object_to_time_t(obj, rnd), time_t) @@ -620,41 +643,51 @@ class TestPytime(unittest.TestCase): @support.cpython_only def test_timeval(self): from _testcapi import pytime_object_to_timeval + + # Conversion giving the same result for all rounding methods + for rnd in ALL_ROUNDING_METHODS: + for obj, timeval in ( + # int + (0, (0, 0)), + (-1, (-1, 0)), + + # float + (-1.0, (-1, 0)), + (-1.2, (-2, 800000)), + (-1e-6, (-1, 999999)), + (1e-6, (0, 1)), + ): + with self.subTest(obj=obj, round=rnd, timeval=timeval): + self.assertEqual(pytime_object_to_timeval(obj, rnd), + timeval) + + # Conversion giving different results depending on the rounding method + DOWN = _PyTime_ROUND_DOWN + UP = _PyTime_ROUND_UP + HALF_UP = _PyTime_ROUND_HALF_UP for obj, timeval, rnd in ( - # Round towards zero - (0, (0, 0), _PyTime_ROUND_DOWN), - (-1, (-1, 0), _PyTime_ROUND_DOWN), - (-1.0, (-1, 0), _PyTime_ROUND_DOWN), - (1e-6, (0, 1), _PyTime_ROUND_DOWN), - (1e-7, (0, 0), _PyTime_ROUND_DOWN), - (-1e-6, (-1, 999999), _PyTime_ROUND_DOWN), - (-1e-7, (-1, 999999), _PyTime_ROUND_DOWN), - (-1.2, (-2, 800000), _PyTime_ROUND_DOWN), - (0.9999999, (0, 999999), _PyTime_ROUND_DOWN), - (0.0000041, (0, 4), _PyTime_ROUND_DOWN), - (1.1234560, (1, 123456), _PyTime_ROUND_DOWN), - (1.1234569, (1, 123456), _PyTime_ROUND_DOWN), - (-0.0000040, (-1, 999996), _PyTime_ROUND_DOWN), - (-0.0000041, (-1, 999995), _PyTime_ROUND_DOWN), - (-1.1234560, (-2, 876544), _PyTime_ROUND_DOWN), - (-1.1234561, (-2, 876543), _PyTime_ROUND_DOWN), - # Round away from zero - (0, (0, 0), _PyTime_ROUND_UP), - (-1, (-1, 0), _PyTime_ROUND_UP), - (-1.0, (-1, 0), _PyTime_ROUND_UP), - (1e-6, (0, 1), _PyTime_ROUND_UP), - (1e-7, (0, 1), _PyTime_ROUND_UP), - (-1e-6, (-1, 999999), _PyTime_ROUND_UP), - (-1e-7, (-1, 999999), _PyTime_ROUND_UP), - (-1.2, (-2, 800000), _PyTime_ROUND_UP), - (0.9999999, (1, 0), _PyTime_ROUND_UP), - (0.0000041, (0, 5), _PyTime_ROUND_UP), - (1.1234560, (1, 123457), _PyTime_ROUND_UP), - (1.1234569, (1, 123457), _PyTime_ROUND_UP), - (-0.0000040, (-1, 999996), _PyTime_ROUND_UP), - (-0.0000041, (-1, 999995), _PyTime_ROUND_UP), - (-1.1234560, (-2, 876544), _PyTime_ROUND_UP), - (-1.1234561, (-2, 876543), _PyTime_ROUND_UP), + (-1e-7, (-1, 999999), DOWN), + (-1e-7, (0, 0), UP), + (-1e-7, (0, 0), HALF_UP), + + (1e-7, (0, 0), DOWN), + (1e-7, (0, 1), UP), + (1e-7, (0, 0), HALF_UP), + + (0.9999999, (0, 999999), DOWN), + (0.9999999, (1, 0), UP), + (0.9999999, (1, 0), HALF_UP), + + # half up + (-0.6e-6, (-1, 999999), HALF_UP), + # skipped, -0.5e-6 is inexact in base 2 + #(-0.5e-6, (-1, 999999), HALF_UP), + (-0.4e-6, (0, 0), HALF_UP), + + (0.4e-6, (0, 0), HALF_UP), + # skipped, 0.5e-6 is inexact in base 2 + #(0.5e-6, (0, 1), HALF_UP), + (0.6e-6, (0, 1), HALF_UP), ): with self.subTest(obj=obj, round=rnd, timeval=timeval): self.assertEqual(pytime_object_to_timeval(obj, rnd), timeval) @@ -667,35 +700,48 @@ class TestPytime(unittest.TestCase): @support.cpython_only def test_timespec(self): from _testcapi import pytime_object_to_timespec + + # Conversion giving the same result for all rounding methods + for rnd in ALL_ROUNDING_METHODS: + for obj, timespec in ( + # int + (0, (0, 0)), + (-1, (-1, 0)), + + # float + (-1.0, (-1, 0)), + (-1e-9, (-1, 999999999)), + (1e-9, (0, 1)), + (-1.2, (-2, 800000000)), + ): + with self.subTest(obj=obj, round=rnd, timespec=timespec): + self.assertEqual(pytime_object_to_timespec(obj, rnd), + timespec) + + DOWN = _PyTime_ROUND_DOWN + UP = _PyTime_ROUND_UP + HALF_UP = _PyTime_ROUND_HALF_UP for obj, timespec, rnd in ( - # Round towards zero - (0, (0, 0), _PyTime_ROUND_DOWN), - (-1, (-1, 0), _PyTime_ROUND_DOWN), - (-1.0, (-1, 0), _PyTime_ROUND_DOWN), - (1e-9, (0, 1), _PyTime_ROUND_DOWN), - (1e-10, (0, 0), _PyTime_ROUND_DOWN), - (-1e-9, (-1, 999999999), _PyTime_ROUND_DOWN), - (-1e-10, (-1, 999999999), _PyTime_ROUND_DOWN), - (-1.2, (-2, 800000000), _PyTime_ROUND_DOWN), - (0.9999999999, (0, 999999999), _PyTime_ROUND_DOWN), - (1.1234567890, (1, 123456789), _PyTime_ROUND_DOWN), - (1.1234567899, (1, 123456789), _PyTime_ROUND_DOWN), - (-1.1234567890, (-2, 876543211), _PyTime_ROUND_DOWN), - (-1.1234567891, (-2, 876543210), _PyTime_ROUND_DOWN), - # Round away from zero - (0, (0, 0), _PyTime_ROUND_UP), - (-1, (-1, 0), _PyTime_ROUND_UP), - (-1.0, (-1, 0), _PyTime_ROUND_UP), - (1e-9, (0, 1), _PyTime_ROUND_UP), - (1e-10, (0, 1), _PyTime_ROUND_UP), - (-1e-9, (-1, 999999999), _PyTime_ROUND_UP), - (-1e-10, (-1, 999999999), _PyTime_ROUND_UP), - (-1.2, (-2, 800000000), _PyTime_ROUND_UP), - (0.9999999999, (1, 0), _PyTime_ROUND_UP), - (1.1234567890, (1, 123456790), _PyTime_ROUND_UP), - (1.1234567899, (1, 123456790), _PyTime_ROUND_UP), - (-1.1234567890, (-2, 876543211), _PyTime_ROUND_UP), - (-1.1234567891, (-2, 876543210), _PyTime_ROUND_UP), + (-1e-10, (-1, 999999999), DOWN), + (-1e-10, (0, 0), UP), + (-1e-10, (0, 0), HALF_UP), + + (1e-10, (0, 0), DOWN), + (1e-10, (0, 1), UP), + (1e-10, (0, 0), HALF_UP), + + (0.9999999999, (0, 999999999), DOWN), + (0.9999999999, (1, 0), UP), + + # half up + (-0.6e-9, (-1, 999999999), HALF_UP), + # skipped, 0.5e-6 is inexact in base 2 + #(-0.5e-9, (-1, 999999999), HALF_UP), + (-0.4e-9, (0, 0), HALF_UP), + + (0.4e-9, (0, 0), HALF_UP), + (0.5e-9, (0, 1), HALF_UP), + (0.6e-9, (0, 1), HALF_UP), ): with self.subTest(obj=obj, round=rnd, timespec=timespec): self.assertEqual(pytime_object_to_timespec(obj, rnd), timespec) diff -r d4e1cc403175 -r 0ad37807dd4c Modules/_testcapimodule.c --- a/Modules/_testcapimodule.c Sat Sep 05 00:10:05 2015 +0200 +++ b/Modules/_testcapimodule.c Sat Sep 05 00:13:47 2015 +0200 @@ -2582,7 +2582,9 @@ run_in_subinterp(PyObject *self, PyObjec static int check_time_rounding(int round) { - if (round != _PyTime_ROUND_DOWN && round != _PyTime_ROUND_UP) { + if (round != _PyTime_ROUND_DOWN + && round != _PyTime_ROUND_UP + && round != _PyTime_ROUND_HALF_UP) { PyErr_SetString(PyExc_ValueError, "invalid rounding"); return -1; } diff -r d4e1cc403175 -r 0ad37807dd4c Python/pytime.c --- a/Python/pytime.c Sat Sep 05 00:10:05 2015 +0200 +++ b/Python/pytime.c Sat Sep 05 00:13:47 2015 +0200 @@ -174,24 +174,27 @@ static int d = PyFloat_AsDouble(obj); floatpart = modf(d, &intpart); - if (floatpart < 0) { - floatpart = 1.0 + floatpart; - intpart -= 1.0; - } floatpart *= denominator; - if (round == _PyTime_ROUND_UP) { + if (round == _PyTime_ROUND_HALF_UP) + floatpart = _PyTime_RoundHalfUp(floatpart); + else if (round == _PyTime_ROUND_UP) { if (intpart >= 0) { floatpart = ceil(floatpart); - if (floatpart >= denominator) { - floatpart = 0.0; - intpart += 1.0; - } } else { floatpart = floor(floatpart); } } + if (floatpart >= denominator) { + floatpart -= denominator; + intpart += 1.0; + } + else if (floatpart < 0) { + floatpart += denominator; + intpart -= 1.0; + } + assert(0.0 <= floatpart && floatpart < denominator); *sec = (time_t)intpart; err = intpart - (double)*sec; @@ -219,7 +222,9 @@ int double d, intpart, err; d = PyFloat_AsDouble(obj); - if (round == _PyTime_ROUND_UP) { + if (round == _PyTime_ROUND_HALF_UP) + d = _PyTime_RoundHalfUp(d); + else if (round == _PyTime_ROUND_UP) { if (d >= 0) d = ceil(d); else