5

As the title says, I would like to know what is the time complexity of math.log2(x). I know that it is possible to write such a function in C in O(1) complexity, but I could not find any information about the implementation of this function in Python.

Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
Saviour
  • 302
  • 2
  • 11
  • You could start directly in CPython source code - https://github.com/python/cpython/blob/be143ec99674ba38c5811f34cdb85ef39c2dc8f8/Modules/mathmodule.c#L2299 – Andrej Kesely Dec 09 '19 at 20:14
  • `math.log2` works on floating points (with a fixed width), hence the operation itself runs in constant time. The conversion of an arbitrary large int to a float might take some non-constant time. – Willem Van Onsem Dec 09 '19 at 20:15
  • The conversion of an arbitrarily large int to a float should take constant time, since both the exponent and the most significant bits can be read off in constant time. – kaya3 Dec 09 '19 at 20:17

1 Answers1

9

In the CPython implementation of Python log2 is implemented as the following C function, plus a layer above this in C that handles error reporting and handles integers specially but ultimately even in the integer case it is the code below that performs the logarithm.

The logic is basically to use a standard C log2 function if one is available otherwise compute log2 in terms of log. In any case it is O(1) but with a relatively high constant factor due to all the layers of checks and sanitization.

/*
   log2: log to base 2.

   Uses an algorithm that should:

     (a) produce exact results for powers of 2, and
     (b) give a monotonic log2 (for positive finite floats),
         assuming that the system log is monotonic.
*/

static double
m_log2(double x)
{
    if (!Py_IS_FINITE(x)) {
        if (Py_IS_NAN(x))
            return x; /* log2(nan) = nan */
        else if (x > 0.0)
            return x; /* log2(+inf) = +inf */
        else {
            errno = EDOM;
            return Py_NAN; /* log2(-inf) = nan, invalid-operation */
        }
    }

    if (x > 0.0) {
#ifdef HAVE_LOG2
        return log2(x);
#else
        double m;
        int e;
        m = frexp(x, &e);
        /* We want log2(m * 2**e) == log(m) / log(2) + e.  Care is needed when
         * x is just greater than 1.0: in that case e is 1, log(m) is negative,
         * and we get significant cancellation error from the addition of
         * log(m) / log(2) to e.  The slight rewrite of the expression below
         * avoids this problem.
         */
        if (x >= 1.0) {
            return log(2.0 * m) / log(2.0) + (e - 1);
        }
        else {
            return log(m) / log(2.0) + e;
        }
#endif
    }
    else if (x == 0.0) {
        errno = EDOM;
        return -Py_HUGE_VAL; /* log2(0) = -inf, divide-by-zero */
    }
    else {
        errno = EDOM;
        return Py_NAN; /* log2(-inf) = nan, invalid-operation */
    }
}
jwezorek
  • 8,592
  • 1
  • 29
  • 46