From 0381e3f16a0d390e956c8adbe905d48e92de9cc6 Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Sat, 8 May 2010 14:35:02 +0000 Subject: Issue #8644: Improve accuracy of timedelta.total_seconds, by doing intermediate computations with integer arithmetic instead of floating point. td.total_seconds() now agrees with td / timedelta(seconds = 1). Thanks Alexander Belopolsky for the patch. --- Doc/library/datetime.rst | 5 ++++- Lib/test/test_datetime.py | 5 +++++ Misc/NEWS | 5 +++++ Modules/datetimemodule.c | 22 +++++++++++++++++++--- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 758e4d2..908a792 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -287,7 +287,10 @@ Instance methods: .. method:: timedelta.total_seconds() Return the total number of seconds contained in the duration. Equivalent to - ``td.microseconds / 1000000 + td.seconds + td.days * 24 * 3600``. + ``td / timedelta(seconds=1)``. + + Note that for very large time intervals (greater than 270 years on + most platforms) this method will lose microsecond accuracy. .. versionadded:: 3.2 diff --git a/Lib/test/test_datetime.py b/Lib/test/test_datetime.py index a5b53fb..f65fbca 100644 --- a/Lib/test/test_datetime.py +++ b/Lib/test/test_datetime.py @@ -264,6 +264,11 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase): for total_seconds in [123456.789012, -123456.789012, 0.123456, 0, 1e6]: td = timedelta(seconds=total_seconds) self.assertEqual(td.total_seconds(), total_seconds) + # Issue8644: Test that td.total_seconds() has the same + # accuracy as td / timedelta(seconds=1). + for ms in [-1, -2, -123]: + td = timedelta(microseconds=ms) + self.assertEqual(td.total_seconds(), td / timedelta(seconds=1)) def test_carries(self): t1 = timedelta(days=100, diff --git a/Misc/NEWS b/Misc/NEWS index 77aa054..7a37f79 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -1105,6 +1105,11 @@ Library Extension Modules ----------------- +- Issue #8644: The accuracy of td.total_seconds() has been improved (by + calculating with integer arithmetic instead of float arithmetic internally): + the result is now always correctly rounded, and is equivalent to td / + timedelta(seconds=1). + - Issue #2706: Allow division of a timedelta by another timedelta: timedelta / timedelta, timedelta % timedelta, timedelta // timedelta and divmod(timedelta, timedelta) are all supported. diff --git a/Modules/datetimemodule.c b/Modules/datetimemodule.c index 83dab2c..d4714bd 100644 --- a/Modules/datetimemodule.c +++ b/Modules/datetimemodule.c @@ -2211,9 +2211,25 @@ delta_getstate(PyDateTime_Delta *self) static PyObject * delta_total_seconds(PyObject *self) { - return PyFloat_FromDouble(GET_TD_MICROSECONDS(self) / 1000000.0 + - GET_TD_SECONDS(self) + - GET_TD_DAYS(self) * 24.0 * 3600.0); + PyObject *total_seconds; + PyObject *total_microseconds; + PyObject *one_million; + + total_microseconds = delta_to_microseconds((PyDateTime_Delta *)self); + if (total_microseconds == NULL) + return NULL; + + one_million = PyLong_FromLong(1000000L); + if (one_million == NULL) { + Py_DECREF(total_microseconds); + return NULL; + } + + total_seconds = PyNumber_TrueDivide(total_microseconds, one_million); + + Py_DECREF(total_microseconds); + Py_DECREF(one_million); + return total_seconds; } static PyObject * -- cgit v0.12