TP: Implementing the Exponential Function (5/5)

TP: Implementing the Exponential Function (5/5)#

Attention

This exercise is marked with a ♣ and is therefore of a more advanced level; do not hesitate to skip it if it seems difficult to you in order to not waste too much time, and to come back to it later.

Part 5: Optimization ♣#

In this specific case, it is not very efficient to reuse the functions puissance and

factorielle: the calculations are performed several times! Indeed, as we have written our exponential function, to calculate \(x^{r+1}\) we restart the calculation from the beginning

(new call to the power function) without using the fact that we have already calculated \(x^r\)

and that \(x^{r+1} = x^r \times x\) (and similarly for the calculation of the factorial which is restarted from the beginning with each call to the factorial function, while

\((r+1)! = r! \times (r+1)\)). We will write a new, more efficient version of the exponential function, which does not call the power or factorial functions to avoid this problem of calculations being performed multiple times.

Copy and paste here the egal function from part 3:

### BEGIN SOLUTION
def egal(x, y, epsilon):
    v = abs(x-y)
    return ((v < epsilon * abs(x)) and (v < epsilon * abs(y)))
### END SOLUTION

Complete the function below that calculates the exponential to a given precision without

using an auxiliary function (except egal), and doing so more efficiently. To

do this, you will need three accumulation variables: that of the power, that

of the factorial and that of the exponential.

def expRapide(x, epsilon):
    """ Calcul rapide de la fonction exponentielle à précision donnée
     * Parametre x un nombre de type double
     * Parametre epsilon un nombre de type double
     * Parametre e^x avec précision epsilon
    """
    ### BEGIN SOLUTION
    e1 = 0
    e2 = 1
    p = 1
    f = 1
    i = 1
    while(not egal(e1,e2,epsilon)):
        e1 = e2
        p *= x
        f *= i
        e2 += p / f
        i += 1
    return e2
    ### END SOLUTION

TODO below: verify the level of precision, doesn’t seem to work

expRapide(5, 0.000000001) # 148.413159
148.41315907883663
expRapide(3, 0.000000001) # 20.085537
20.085536921517665
expRapide(1, 0.000000001) # 2.718282
2.7182818282861687

Evaluate the performance of the expRapide function again using timeit. Is it

really faster?

import time
start = time.time()
expRapide(10, 0.00000001)
print(time.time() - start)
0.00012564659118652344

Summary#

You now have a significantly faster implementation of the exponential function.

However, should you always reimplement everything rather than reuse it? No, especially

not:

“Early optimisation is the root of all evil”

– Donald Knuth

Here, one could, for example, obtain the same performance without code duplication by

memoization (storing the already calculated values of \(n!\) and \(x^n\) to avoid

recalculating them). In general, it should be treated on a case-by-case basis, taking into account the trade-off

between development time and required performance, complexity issues (see

future courses), etc.