[Python-checkins] cpython: Issue #23517: fromtimestamp() and utcfromtimestamp() methods of

victor.stinner python-checkins at python.org
Wed Sep 9 01:04:29 CEST 2015


https://hg.python.org/cpython/rev/171d5590ebc3
changeset:   97782:171d5590ebc3
user:        Victor Stinner <victor.stinner at gmail.com>
date:        Wed Sep 09 01:02:23 2015 +0200
summary:
  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.

files:
  Include/pytime.h           |    9 +-
  Lib/datetime.py            |    2 +-
  Lib/test/datetimetester.py |    4 +-
  Lib/test/test_time.py      |  237 +++++++++++-------------
  Misc/NEWS                  |    6 +-
  Modules/_datetimemodule.c  |    2 +-
  Modules/_testcapimodule.c  |    2 +-
  Python/pytime.c            |   71 +++----
  8 files changed, 148 insertions(+), 185 deletions(-)


diff --git a/Include/pytime.h b/Include/pytime.h
--- a/Include/pytime.h
+++ b/Include/pytime.h
@@ -31,9 +31,9 @@
     /* Round towards infinity (+inf).
        For example, used for timeout to wait "at least" N seconds. */
     _PyTime_ROUND_CEILING=1,
-    /* Round to nearest with ties going away from zero.
+    /* Round to nearest with ties going to nearest even integer.
        For example, used to round from a Python float. */
-    _PyTime_ROUND_HALF_UP
+    _PyTime_ROUND_HALF_EVEN
 } _PyTime_round_t;
 
 /* Convert a time_t to a PyLong. */
@@ -44,8 +44,9 @@
 PyAPI_FUNC(time_t) _PyLong_AsTime_t(
     PyObject *obj);
 
-/* Round to nearest with ties going away from zero (_PyTime_ROUND_HALF_UP). */
-PyAPI_FUNC(double) _PyTime_RoundHalfUp(
+/* Round to nearest with ties going to nearest even integer
+   (_PyTime_ROUND_HALF_EVEN) */
+PyAPI_FUNC(double) _PyTime_RoundHalfEven(
     double x);
 
 /* Convert a number of seconds, int or float, to time_t. */
diff --git a/Lib/datetime.py b/Lib/datetime.py
--- a/Lib/datetime.py
+++ b/Lib/datetime.py
@@ -1380,7 +1380,7 @@
         A timezone info object may be passed in as well.
         """
         frac, t = _math.modf(t)
-        us = _round_half_up(frac * 1e6)
+        us = round(frac * 1e6)
         if us >= 1000000:
             t += 1
             us -= 1000000
diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py
--- a/Lib/test/datetimetester.py
+++ b/Lib/test/datetimetester.py
@@ -1874,7 +1874,7 @@
                 self.assertEqual(t, zero)
                 t = fts(-1/2**7)
                 self.assertEqual(t.second, 59)
-                self.assertEqual(t.microsecond, 992187)
+                self.assertEqual(t.microsecond, 992188)
 
             t = fts(1e-7)
             self.assertEqual(t, zero)
@@ -1888,7 +1888,7 @@
             self.assertEqual(t.microsecond, 0)
             t = fts(1/2**7)
             self.assertEqual(t.second, 0)
-            self.assertEqual(t.microsecond, 7813)
+            self.assertEqual(t.microsecond, 7812)
 
     def test_insane_fromtimestamp(self):
         # It's possible that some platform maps time_t to double,
diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py
--- a/Lib/test/test_time.py
+++ b/Lib/test/test_time.py
@@ -30,11 +30,11 @@
     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 @@
         # 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 @@
         # 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 @@
             (-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 @@
                 (-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 @@
         # 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 @@
 
         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 @@
 
         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 @@
 
         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 @@
         # 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 @@
         # 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 @@
         # 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)
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -20,9 +20,9 @@
 - Issue #22241: timezone.utc name is now plain 'UTC', not 'UTC-00:00'.
 
 - Issue #23517: fromtimestamp() and utcfromtimestamp() methods of
-  datetime.datetime now round microseconds to nearest with ties going away from
-  zero (ROUND_HALF_UP), as Python 2 and Python older than 3.3, instead of
-  rounding towards -Infinity (ROUND_FLOOR).
+  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).
 
 - Issue #23552: Timeit now warns when there is substantial (4x) variance
   between best and worst times. Patch from Serhiy Storchaka.
diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c
--- a/Modules/_datetimemodule.c
+++ b/Modules/_datetimemodule.c
@@ -4103,7 +4103,7 @@
     long us;
 
     if (_PyTime_ObjectToTimeval(timestamp,
-                                &timet, &us, _PyTime_ROUND_HALF_UP) == -1)
+                                &timet, &us, _PyTime_ROUND_HALF_EVEN) == -1)
         return NULL;
 
     return datetime_from_timet_and_us(cls, f, timet, (int)us, tzinfo);
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -2648,7 +2648,7 @@
 {
     if (round != _PyTime_ROUND_FLOOR
         && round != _PyTime_ROUND_CEILING
-        && round != _PyTime_ROUND_HALF_UP) {
+        && round != _PyTime_ROUND_HALF_EVEN) {
         PyErr_SetString(PyExc_ValueError, "invalid rounding");
         return -1;
     }
diff --git a/Python/pytime.c b/Python/pytime.c
--- a/Python/pytime.c
+++ b/Python/pytime.c
@@ -61,18 +61,15 @@
 }
 
 double
-_PyTime_RoundHalfUp(double x)
+_PyTime_RoundHalfEven(double x)
 {
-    /* volatile avoids optimization changing how numbers are rounded */
-    volatile double d = x;
-    if (d >= 0.0)
-        d = floor(d + 0.5);
-    else
-        d = ceil(d - 0.5);
-    return d;
+    double rounded = round(x);
+    if (fabs(x-rounded) == 0.5)
+        /* halfway case: round to even */
+        rounded = 2.0*round(x/2.0);
+    return rounded;
 }
 
-
 static int
 _PyTime_DoubleToDenominator(double d, time_t *sec, long *numerator,
                             double denominator, _PyTime_round_t round)
@@ -84,8 +81,8 @@
     floatpart = modf(d, &intpart);
 
     floatpart *= denominator;
-    if (round == _PyTime_ROUND_HALF_UP)
-        floatpart = _PyTime_RoundHalfUp(floatpart);
+    if (round == _PyTime_ROUND_HALF_EVEN)
+        floatpart = _PyTime_RoundHalfEven(floatpart);
     else if (round == _PyTime_ROUND_CEILING)
         floatpart = ceil(floatpart);
     else
@@ -140,8 +137,8 @@
         volatile double d;
 
         d = PyFloat_AsDouble(obj);
-        if (round == _PyTime_ROUND_HALF_UP)
-            d = _PyTime_RoundHalfUp(d);
+        if (round == _PyTime_ROUND_HALF_EVEN)
+            d = _PyTime_RoundHalfEven(d);
         else if (round == _PyTime_ROUND_CEILING)
             d = ceil(d);
         else
@@ -266,8 +263,8 @@
     d = value;
     d *= to_nanoseconds;
 
-    if (round == _PyTime_ROUND_HALF_UP)
-        d = _PyTime_RoundHalfUp(d);
+    if (round == _PyTime_ROUND_HALF_EVEN)
+        d = _PyTime_RoundHalfEven(d);
     else if (round == _PyTime_ROUND_CEILING)
         d = ceil(d);
     else
@@ -351,14 +348,16 @@
 }
 
 static _PyTime_t
-_PyTime_Divide(_PyTime_t t, _PyTime_t k, _PyTime_round_t round)
+_PyTime_Divide(const _PyTime_t t, const _PyTime_t k,
+               const _PyTime_round_t round)
 {
     assert(k > 1);
-    if (round == _PyTime_ROUND_HALF_UP) {
-        _PyTime_t x, r;
+    if (round == _PyTime_ROUND_HALF_EVEN) {
+        _PyTime_t x, r, abs_r;
         x = t / k;
         r = t % k;
-        if (Py_ABS(r) >= k / 2) {
+        abs_r = Py_ABS(r);
+        if (abs_r > k / 2 || (abs_r == k / 2 && (Py_ABS(x) & 1))) {
             if (t >= 0)
                 x++;
             else
@@ -370,10 +369,14 @@
         if (t >= 0)
             return (t + k - 1) / k;
         else
+            return t / k;
+    }
+    else {
+        if (t >= 0)
+            return t / k;
+        else
             return (t - (k - 1)) / k;
     }
-    else
-        return t / k;
 }
 
 _PyTime_t
@@ -392,17 +395,12 @@
 _PyTime_AsTimeval_impl(_PyTime_t t, struct timeval *tv, _PyTime_round_t round,
                        int raise)
 {
-    const long k = US_TO_NS;
     _PyTime_t secs, ns;
     int res = 0;
     int usec;
 
     secs = t / SEC_TO_NS;
     ns = t % SEC_TO_NS;
-    if (ns < 0) {
-        ns += SEC_TO_NS;
-        secs -= 1;
-    }
 
 #ifdef MS_WINDOWS
     /* On Windows, timeval.tv_sec is a long (32 bit),
@@ -427,23 +425,12 @@
         res = -1;
 #endif
 
-    if (round == _PyTime_ROUND_HALF_UP) {
-        _PyTime_t r;
-        usec = (int)(ns / k);
-        r = ns % k;
-        if (Py_ABS(r) >= k / 2) {
-            if (ns >= 0)
-                usec++;
-            else
-                usec--;
-        }
+    usec = (int)_PyTime_Divide(ns, US_TO_NS, round);
+    if (usec < 0) {
+        usec += SEC_TO_US;
+        tv->tv_sec -= 1;
     }
-    else if (round == _PyTime_ROUND_CEILING)
-        usec = (int)((ns + k - 1) / k);
-    else
-        usec = (int)(ns / k);
-
-    if (usec >= SEC_TO_US) {
+    else if (usec >= SEC_TO_US) {
         usec -= SEC_TO_US;
         tv->tv_sec += 1;
     }

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list