STINNER Victor <victor.stin...@haypocalc.com> added the comment:

Attached patch adds an optional format argument to time.time():
time.time("float") is the same than time.time(), but
time.time("decimal") returns a Decimal object. The Decimal object
stores the resolution of the clock and doesn't loose lower bits for
big numbers. I configured the Decimal context to be able to store
10,000 years in seconds with a resolution of 1 nanosecond and
ROUND_HALF_EVEN rounding method.

Example: time.time("decimal") returns Decimal('1327495951.346024').

It is really cool for have directly the resolution in the result,
because the resolution may change at each call: time.time() has 3
different implementations (with fallbacks), each has a different
resolution. time.clock() has also 2 implementations (one is used as a
fallback) with different resolution.

The internal time_to_format() takes integer arguments: the floating
part is written as (floatpart, divisor).

If you like the idea, I will also write a patch for time.clock(),
time.wallclock() and time.clock_gettime(), and also maybe for
time.clock_getres().

We may use a registry to allow to add user defined formats, but I
prefer to keep the patch simple (only allow "float" and "decimal"
right now).

----------
Added file: http://bugs.python.org/file24321/time_decimal.patch

_______________________________________
Python tracker <rep...@bugs.python.org>
<http://bugs.python.org/issue11457>
_______________________________________
changeset:   74495:6fcdb1d46abb
tag:         tip
user:        Victor Stinner <vstin...@wyplay.com>
date:        Tue Jan 24 14:27:20 2012 +0100
files:       Doc/library/time.rst Modules/timemodule.c
description:
Add a format optional argument to time.time() to return the current time
as a decimal.Decimal object, instead of a float.


diff --git a/Doc/library/time.rst b/Doc/library/time.rst
--- a/Doc/library/time.rst
+++ b/Doc/library/time.rst
@@ -444,9 +444,10 @@ The module defines the following functio
    :exc:`TypeError` is raised.
 
 
-.. function:: time()
+.. function:: time(format="float")
 
-   Return the time as a floating point number expressed in seconds since the 
epoch,
+   Return the time as a floating point number, or a :class:`decimal.Decimal` if
+   format is ``"decimal"``, expressed in seconds since the epoch,
    in UTC.  Note that even though the time is always returned as a floating 
point
    number, not all systems provide time with a better precision than 1 second.
    While this function normally returns non-decreasing values, it can return a
diff --git a/Modules/timemodule.c b/Modules/timemodule.c
--- a/Modules/timemodule.c
+++ b/Modules/timemodule.c
@@ -44,16 +44,246 @@
 static int floatsleep(double);
 static double floattime(void);
 
+typedef unsigned long floatpart_t;
+
+static PyObject*
+PyLong_FromTimeT(time_t value)
+{
+#ifdef HAVE_LONG_LONG
+    return PyLong_FromUnsignedLongLong(value);
+#else
+    assert(sizeof(time_t) <= sizeof(unsigned long));
+    return PyLong_FromUnsignedLong(value);
+#endif
+}
+
+static PyObject*
+PyLong_FromFloatpartT(floatpart_t value)
+{
+#ifdef HAVE_LONG_LONG
+    return PyLong_FromUnsignedLongLong(value);
+#else
+    assert(sizeof(floatpart_t) <= sizeof(unsigned long));
+    return PyLong_FromUnsignedLong(value);
+#endif
+}
+
+/* Convert a timestamp to a decimal.Decimal object of the requested
+   resolution. */
+static PyObject*
+time_to_decimal(time_t value,
+                floatpart_t floatpart, floatpart_t divisor,
+                int resolution)
+{
+    static PyObject* module = NULL;
+    static PyObject* decimal = NULL;
+    static PyObject* context = NULL;
+    /* 10^resolution cache, dictionary of int=>Decimal */
+    static PyObject* exponents = NULL;
+    PyObject *t = NULL;
+
+    if (!module) {
+        module = PyImport_ImportModuleNoBlock("decimal");
+        if (module == NULL)
+            return NULL;
+    }
+
+    if (!decimal) {
+        decimal = PyObject_GetAttrString(module, "Decimal");
+        if (decimal == NULL)
+            return NULL;
+    }
+
+    if (context == NULL)
+    {
+        /* context = decimal.Context(22, rounding=decimal.ROUND_HALF_EVEN) */
+        PyObject *context_class, *rounding;
+        /* Use 12 decimal digits to store 10,000 years in seconds + 9
+           decimal digits for the floating part in nanoseconds + 1 decimal
+           digit to round correctly */
+        context_class = PyObject_GetAttrString(module, "Context");
+        if (context_class == NULL)
+            return NULL;
+        rounding = PyObject_GetAttrString(module, "ROUND_HALF_EVEN");
+        if (rounding == NULL)
+        {
+            Py_DECREF(context_class);
+            return NULL;
+        }
+        context = PyObject_CallFunction(context_class, "iO", 22, rounding);
+        Py_DECREF(context_class);
+        Py_DECREF(rounding);
+        if (context == NULL)
+            return NULL;
+    }
+
+    /* t = decimal.Decimal(value, context) */
+    if (value) {
+        PyObject *f = PyLong_FromTimeT(value);
+        t = PyObject_CallFunction(decimal, "OO", f, context);
+        Py_CLEAR(f);
+    }
+    else {
+        t = PyObject_CallFunction(decimal, "iO", 0, context);
+    }
+    if (t == NULL)
+        return NULL;
+
+    if (floatpart)
+    {
+        /* t += decimal.Decimal(floatpart, ctx) / decimal.Decimal(divisor, 
ctx) */
+        PyObject *a, *b, *c, *d, *x;
+
+        x = PyLong_FromFloatpartT(floatpart);
+        if (x == NULL)
+            goto error;
+        a = PyObject_CallFunction(decimal, "OO", x, context);
+        Py_CLEAR(x);
+        if (a == NULL)
+            goto error;
+
+        x = PyLong_FromFloatpartT(divisor);
+        if (x == NULL)
+        {
+            Py_DECREF(a);
+            goto error;
+        }
+        b = PyObject_CallFunction(decimal, "OO", x, context);
+        Py_CLEAR(x);
+        if (b == NULL)
+        {
+            Py_DECREF(a);
+            goto error;
+        }
+
+        c = PyNumber_TrueDivide(a, b);
+        Py_DECREF(a);
+        Py_DECREF(b);
+        if (c == NULL)
+            goto error;
+
+        d = PyNumber_Add(t, c);
+        Py_DECREF(c);
+        if (d == NULL)
+            goto error;
+        Py_DECREF(t);
+        t = d;
+    }
+
+    if (resolution != 0) {
+        PyObject *key, *exponent, *quantized;
+
+        if (exponents == NULL) {
+            exponents = PyDict_New();
+            if (exponents == NULL)
+                goto error;
+        }
+
+        key = PyLong_FromLong(resolution);
+        if (key == NULL)
+            goto error;
+        exponent = PyDict_GetItem(exponents, key);
+        if (exponent == NULL) {
+            /* exponent = decimal.Decimal(10) ** decimal.Decimal(resolution) */
+            PyObject *ten, *pow;
+
+            ten = PyObject_CallFunction(decimal, "i", 10);
+            if (ten == NULL) {
+                Py_DECREF(key);
+                goto error;
+            }
+
+            pow = PyObject_CallFunction(decimal, "O", key);
+            if (pow == NULL) {
+                Py_DECREF(key);
+                Py_DECREF(ten);
+                goto error;
+            }
+
+            exponent = PyNumber_Power(ten, pow, Py_None);
+            Py_DECREF(ten);
+            Py_DECREF(pow);
+            if (exponent == NULL) {
+                Py_DECREF(key);
+                goto error;
+            }
+
+            if (PyDict_SetItem(exponents, key, exponent) < 0) {
+                Py_DECREF(key);
+                Py_DECREF(exponent);
+                goto error;
+            }
+            Py_DECREF(key);
+        }
+
+        /* t = t.quantize(exponent, None, context) */
+        quantized = PyObject_CallMethod(t, "quantize", "OOO", exponent, 
Py_None, context);
+        if (quantized == NULL)
+            goto error;
+        Py_DECREF(t);
+        t = quantized;
+    }
+    return t;
+
+error:
+    Py_XDECREF(t);
+    return NULL;
+}
+
+static PyObject*
+time_to_float(time_t value,
+              floatpart_t floatpart, floatpart_t divisor,
+              int resolution)
+{
+    double t;
+    t = (double)value;
+    t += (double)floatpart / (double)divisor;
+    return PyFloat_FromDouble(t);
+}
+
+/* Convert an integer timestamp to the requested format. Arguments:
+    - value: int or float
+    - floatpart: floating part, int or float, can be zero
+    - divisor: divisor of the floating part, int or float, cannot be zero
+    - format: "float", "decimal", "tuple"
+
+   The result type depends on the requested format.
+
+   Raise a ValueError if the format is unknown. */
+static PyObject*
+time_to_format(time_t value,
+               floatpart_t floatpart, floatpart_t divisor,
+               int resolution,
+               const char *format)
+{
+    assert(divisor != 0);
+    if (!format || strcmp(format, "float") == 0) {
+        return time_to_float(value, floatpart, divisor, resolution);
+    }
+    else if (strcmp(format, "decimal") == 0) {
+        return time_to_decimal(value, floatpart, divisor, resolution);
+    }
+    else {
+        PyErr_Format(PyExc_ValueError, "Unknown format: %s", format);
+        return NULL;
+    }
+}
+
 static PyObject *
-time_time(PyObject *self, PyObject *unused)
+time_time(PyObject *self, PyObject *args)
 {
-    double secs;
-    secs = floattime();
-    if (secs == 0.0) {
+    const char *format = NULL;
+    _PyTime_timeval t;
+
+    if (!PyArg_ParseTuple(args, "|s:time", &format))
+        return NULL;
+
+    _PyTime_gettimeofday(&t);
+    if (t.tv_sec == 0 && t.tv_usec == 0) {
         PyErr_SetFromErrno(PyExc_IOError);
         return NULL;
     }
-    return PyFloat_FromDouble(secs);
+    return time_to_format(t.tv_sec, t.tv_usec, 1000000, -6, format);
 }
 
 PyDoc_STRVAR(time_doc,
@@ -893,7 +1123,7 @@ PyInit_timezone(PyObject *m) {
 
 
 static PyMethodDef time_methods[] = {
-    {"time",            time_time, METH_NOARGS, time_doc},
+    {"time",            time_time, METH_VARARGS, time_doc},
 #if (defined(MS_WINDOWS) && !defined(__BORLANDC__)) || defined(HAVE_CLOCK)
     {"clock",           time_clock, METH_NOARGS, clock_doc},
 #endif

_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
http://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to