Issue #23517: fromtimestamp() and utcfromtimestamp() methods of

datetime.datetime now round microseconds to nearest with ties going to nearest
even integer (ROUND_HALF_EVEN), as round(float), instead of rounding towards
-Infinity (ROUND_FLOOR).

pytime API: replace _PyTime_ROUND_HALF_UP with _PyTime_ROUND_HALF_EVEN. Fix
also _PyTime_Divide() for negative numbers.

_PyTime_AsTimeval_impl() now reuses _PyTime_Divide() instead of reimplementing
rounding modes.
This commit is contained in:
Victor Stinner 2015-09-09 01:02:23 +02:00
parent 69cc487df4
commit 7667f58151
8 changed files with 148 additions and 185 deletions

View file

@ -30,11 +30,11 @@ class _PyTime(enum.IntEnum):
ROUND_FLOOR = 0
# Round towards infinity (+inf)
ROUND_CEILING = 1
# Round to nearest with ties going away from zero
ROUND_HALF_UP = 2
# Round to nearest with ties going to nearest even integer
ROUND_HALF_EVEN = 2
ALL_ROUNDING_METHODS = (_PyTime.ROUND_FLOOR, _PyTime.ROUND_CEILING,
_PyTime.ROUND_HALF_UP)
_PyTime.ROUND_HALF_EVEN)
class TimeTestCase(unittest.TestCase):
@ -639,27 +639,26 @@ class TestPytime(unittest.TestCase):
# Conversion giving different results depending on the rounding method
FLOOR = _PyTime.ROUND_FLOOR
CEILING = _PyTime.ROUND_CEILING
HALF_UP = _PyTime.ROUND_HALF_UP
for obj, time_t, rnd in (
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
for obj, seconds, rnd in (
(-1.9, -2, FLOOR),
(-1.9, -1, CEILING),
(-1.9, -2, HALF_UP),
(-1.9, -2, HALF_EVEN),
(1.9, 1, FLOOR),
(1.9, 2, CEILING),
(1.9, 2, HALF_UP),
(1.9, 2, HALF_EVEN),
# half up
(-0.999, -1, HALF_UP),
(-0.510, -1, HALF_UP),
(-0.500, -1, HALF_UP),
(-0.490, 0, HALF_UP),
( 0.490, 0, HALF_UP),
( 0.500, 1, HALF_UP),
( 0.510, 1, HALF_UP),
( 0.999, 1, HALF_UP),
# half even
(-1.5, -2, HALF_EVEN),
(-0.9, -1, HALF_EVEN),
(-0.5, 0, HALF_EVEN),
( 0.5, 0, HALF_EVEN),
( 0.9, 1, HALF_EVEN),
( 1.5, 2, HALF_EVEN),
):
self.assertEqual(pytime_object_to_time_t(obj, rnd), time_t)
with self.subTest(obj=obj, round=rnd, seconds=seconds):
self.assertEqual(pytime_object_to_time_t(obj, rnd), seconds)
# Test OverflowError
rnd = _PyTime.ROUND_FLOOR
@ -691,15 +690,15 @@ class TestPytime(unittest.TestCase):
# Conversion giving different results depending on the rounding method
FLOOR = _PyTime.ROUND_FLOOR
CEILING = _PyTime.ROUND_CEILING
HALF_UP = _PyTime.ROUND_HALF_UP
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
for obj, timespec, rnd in (
# Round towards minus infinity (-inf)
(-1e-10, (0, 0), CEILING),
(-1e-10, (-1, 999999999), FLOOR),
(-1e-10, (0, 0), HALF_UP),
(-1e-10, (0, 0), HALF_EVEN),
(1e-10, (0, 0), FLOOR),
(1e-10, (0, 1), CEILING),
(1e-10, (0, 0), HALF_UP),
(1e-10, (0, 0), HALF_EVEN),
(0.9999999999, (0, 999999999), FLOOR),
(0.9999999999, (1, 0), CEILING),
@ -714,15 +713,13 @@ class TestPytime(unittest.TestCase):
(-1.1234567890, (-2, 876543211), CEILING),
(-1.1234567891, (-2, 876543211), CEILING),
# 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),
# half even
(-1.5e-9, (-1, 999999998), HALF_EVEN),
(-0.9e-9, (-1, 999999999), HALF_EVEN),
(-0.5e-9, (0, 0), HALF_EVEN),
(0.5e-9, (0, 0), HALF_EVEN),
(0.9e-9, (0, 1), HALF_EVEN),
(1.5e-9, (0, 2), HALF_EVEN),
):
with self.subTest(obj=obj, round=rnd, timespec=timespec):
self.assertEqual(pytime_object_to_timespec(obj, rnd), timespec)
@ -823,10 +820,10 @@ class TestPyTime_t(unittest.TestCase):
(-7.0, -7 * SEC_TO_NS),
# nanosecond are kept for value <= 2^23 seconds,
# except 2**23-1e-9 with HALF_UP
(2**22 - 1e-9, 4194303999999999),
(2**22, 4194304000000000),
(2**22 + 1e-9, 4194304000000001),
(2**23 - 1e-9, 8388607999999999),
(2**23, 8388608000000000),
# start loosing precision for value > 2^23 seconds
@ -859,38 +856,31 @@ class TestPyTime_t(unittest.TestCase):
# Conversion giving different results depending on the rounding method
FLOOR = _PyTime.ROUND_FLOOR
CEILING = _PyTime.ROUND_CEILING
HALF_UP = _PyTime.ROUND_HALF_UP
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
for obj, ts, rnd in (
# close to zero
( 1e-10, 0, FLOOR),
( 1e-10, 1, CEILING),
( 1e-10, 0, HALF_UP),
( 1e-10, 0, HALF_EVEN),
(-1e-10, -1, FLOOR),
(-1e-10, 0, CEILING),
(-1e-10, 0, HALF_UP),
(-1e-10, 0, HALF_EVEN),
# test rounding of the last nanosecond
( 1.1234567899, 1123456789, FLOOR),
( 1.1234567899, 1123456790, CEILING),
( 1.1234567899, 1123456790, HALF_UP),
( 1.1234567899, 1123456790, HALF_EVEN),
(-1.1234567899, -1123456790, FLOOR),
(-1.1234567899, -1123456789, CEILING),
(-1.1234567899, -1123456790, HALF_UP),
(-1.1234567899, -1123456790, HALF_EVEN),
# close to 1 second
( 0.9999999999, 999999999, FLOOR),
( 0.9999999999, 1000000000, CEILING),
( 0.9999999999, 1000000000, HALF_UP),
( 0.9999999999, 1000000000, HALF_EVEN),
(-0.9999999999, -1000000000, FLOOR),
(-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),
# Issue #23517: skip HALF_UP test because the result is different
# depending on the FPU and how the compiler optimize the code :-/
#(2**23 - 1e-9, 8388608000000000, HALF_UP),
(-0.9999999999, -1000000000, HALF_EVEN),
):
with self.subTest(obj=obj, round=rnd, timestamp=ts):
self.assertEqual(PyTime_FromSecondsObject(obj, rnd), ts)
@ -958,33 +948,23 @@ class TestPyTime_t(unittest.TestCase):
FLOOR = _PyTime.ROUND_FLOOR
CEILING = _PyTime.ROUND_CEILING
HALF_UP = _PyTime.ROUND_HALF_UP
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
for ns, tv, rnd in (
# nanoseconds
(1, (0, 0), FLOOR),
(1, (0, 1), CEILING),
(1, (0, 0), HALF_UP),
(1, (0, 0), HALF_EVEN),
(-1, (-1, 999999), FLOOR),
(-1, (0, 0), CEILING),
(-1, (0, 0), HALF_UP),
(-1, (0, 0), HALF_EVEN),
# seconds + nanoseconds
(1234567001, (1, 234567), FLOOR),
(1234567001, (1, 234568), CEILING),
(1234567001, (1, 234567), HALF_UP),
(-1234567001, (-2, 765432), FLOOR),
(-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),
# half even
(-1500, (-1, 999998), HALF_EVEN),
(-999, (-1, 999999), HALF_EVEN),
(-500, (0, 0), HALF_EVEN),
(500, (0, 0), HALF_EVEN),
(999, (0, 1), HALF_EVEN),
(1500, (0, 2), HALF_EVEN),
):
with self.subTest(nanoseconds=ns, timeval=tv, round=rnd):
self.assertEqual(PyTime_AsTimeval(ns, rnd), tv)
@ -1027,33 +1007,31 @@ class TestPyTime_t(unittest.TestCase):
FLOOR = _PyTime.ROUND_FLOOR
CEILING = _PyTime.ROUND_CEILING
HALF_UP = _PyTime.ROUND_HALF_UP
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
for ns, ms, rnd in (
# nanoseconds
(1, 0, FLOOR),
(1, 1, CEILING),
(1, 0, HALF_UP),
(-1, 0, FLOOR),
(-1, -1, CEILING),
(-1, 0, HALF_UP),
(1, 0, HALF_EVEN),
(-1, -1, FLOOR),
(-1, 0, CEILING),
(-1, 0, HALF_EVEN),
# seconds + nanoseconds
(1234 * MS_TO_NS + 1, 1234, FLOOR),
(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, -1235, CEILING),
(-1234 * MS_TO_NS - 1, -1234, HALF_UP),
(1234 * MS_TO_NS + 1, 1234, HALF_EVEN),
(-1234 * MS_TO_NS - 1, -1235, FLOOR),
(-1234 * MS_TO_NS - 1, -1234, CEILING),
(-1234 * MS_TO_NS - 1, -1234, HALF_EVEN),
# 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),
(-1500000, -2, HALF_EVEN),
(-999999, -1, HALF_EVEN),
(-500000, 0, HALF_EVEN),
(500000, 0, HALF_EVEN),
(999999, 1, HALF_EVEN),
(1500000, 2, HALF_EVEN),
):
with self.subTest(nanoseconds=ns, milliseconds=ms, round=rnd):
self.assertEqual(PyTime_AsMilliseconds(ns, rnd), ms)
@ -1079,31 +1057,31 @@ class TestPyTime_t(unittest.TestCase):
FLOOR = _PyTime.ROUND_FLOOR
CEILING = _PyTime.ROUND_CEILING
HALF_UP = _PyTime.ROUND_HALF_UP
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
for ns, ms, rnd in (
# nanoseconds
(1, 0, FLOOR),
(1, 1, CEILING),
(1, 0, HALF_UP),
(-1, 0, FLOOR),
(-1, -1, CEILING),
(-1, 0, HALF_UP),
(1, 0, HALF_EVEN),
(-1, -1, FLOOR),
(-1, 0, CEILING),
(-1, 0, HALF_EVEN),
# seconds + nanoseconds
(1234 * US_TO_NS + 1, 1234, FLOOR),
(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, -1235, CEILING),
(-1234 * US_TO_NS - 1, -1234, HALF_UP),
(1234 * US_TO_NS + 1, 1234, HALF_EVEN),
(-1234 * US_TO_NS - 1, -1235, FLOOR),
(-1234 * US_TO_NS - 1, -1234, CEILING),
(-1234 * US_TO_NS - 1, -1234, HALF_EVEN),
# 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),
(-1500, -2, HALF_EVEN),
(-999, -1, HALF_EVEN),
(-500, 0, HALF_EVEN),
(500, 0, HALF_EVEN),
(999, 1, HALF_EVEN),
(1500, 2, HALF_EVEN),
):
with self.subTest(nanoseconds=ns, milliseconds=ms, round=rnd):
self.assertEqual(PyTime_AsMicroseconds(ns, rnd), ms)
@ -1142,23 +1120,23 @@ class TestOldPyTime(unittest.TestCase):
# Conversion giving different results depending on the rounding method
FLOOR = _PyTime.ROUND_FLOOR
CEILING = _PyTime.ROUND_CEILING
HALF_UP = _PyTime.ROUND_HALF_UP
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
for obj, time_t, rnd in (
(-1.9, -2, FLOOR),
(-1.9, -2, HALF_UP),
(-1.9, -2, HALF_EVEN),
(-1.9, -1, CEILING),
(1.9, 1, FLOOR),
(1.9, 2, HALF_UP),
(1.9, 2, HALF_EVEN),
(1.9, 2, CEILING),
(-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),
# half even
(-1.5, -2, HALF_EVEN),
(-0.9, -1, HALF_EVEN),
(-0.5, 0, HALF_EVEN),
( 0.5, 0, HALF_EVEN),
( 0.9, 1, HALF_EVEN),
( 1.5, 2, HALF_EVEN),
):
self.assertEqual(pytime_object_to_time_t(obj, rnd), time_t)
@ -1192,29 +1170,27 @@ class TestOldPyTime(unittest.TestCase):
# Conversion giving different results depending on the rounding method
FLOOR = _PyTime.ROUND_FLOOR
CEILING = _PyTime.ROUND_CEILING
HALF_UP = _PyTime.ROUND_HALF_UP
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
for obj, timeval, rnd in (
(-1e-7, (-1, 999999), FLOOR),
(-1e-7, (0, 0), CEILING),
(-1e-7, (0, 0), HALF_UP),
(-1e-7, (0, 0), HALF_EVEN),
(1e-7, (0, 0), FLOOR),
(1e-7, (0, 1), CEILING),
(1e-7, (0, 0), HALF_UP),
(1e-7, (0, 0), HALF_EVEN),
(0.9999999, (0, 999999), FLOOR),
(0.9999999, (1, 0), CEILING),
(0.9999999, (1, 0), HALF_UP),
(0.9999999, (1, 0), HALF_EVEN),
(-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),
# half even
(-1.5e-6, (-1, 999998), HALF_EVEN),
(-0.9e-6, (-1, 999999), HALF_EVEN),
(-0.5e-6, (0, 0), HALF_EVEN),
(0.5e-6, (0, 0), HALF_EVEN),
(0.9e-6, (0, 1), HALF_EVEN),
(1.5e-6, (0, 2), HALF_EVEN),
):
with self.subTest(obj=obj, round=rnd, timeval=timeval):
self.assertEqual(pytime_object_to_timeval(obj, rnd), timeval)
@ -1248,28 +1224,27 @@ class TestOldPyTime(unittest.TestCase):
# Conversion giving different results depending on the rounding method
FLOOR = _PyTime.ROUND_FLOOR
CEILING = _PyTime.ROUND_CEILING
HALF_UP = _PyTime.ROUND_HALF_UP
HALF_EVEN = _PyTime.ROUND_HALF_EVEN
for obj, timespec, rnd in (
(-1e-10, (-1, 999999999), FLOOR),
(-1e-10, (0, 0), CEILING),
(-1e-10, (0, 0), HALF_UP),
(-1e-10, (0, 0), HALF_EVEN),
(1e-10, (0, 0), FLOOR),
(1e-10, (0, 1), CEILING),
(1e-10, (0, 0), HALF_UP),
(1e-10, (0, 0), HALF_EVEN),
(0.9999999999, (0, 999999999), FLOOR),
(0.9999999999, (1, 0), CEILING),
(0.9999999999, (1, 0), HALF_UP),
(0.9999999999, (1, 0), HALF_EVEN),
(-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),
# half even
(-1.5e-9, (-1, 999999998), HALF_EVEN),
(-0.9e-9, (-1, 999999999), HALF_EVEN),
(-0.5e-9, (0, 0), HALF_EVEN),
(0.5e-9, (0, 0), HALF_EVEN),
(0.9e-9, (0, 1), HALF_EVEN),
(1.5e-9, (0, 2), HALF_EVEN),
):
with self.subTest(obj=obj, round=rnd, timespec=timespec):
self.assertEqual(pytime_object_to_timespec(obj, rnd), timespec)