Vous êtes sur la page 1sur 12

Lycée Max LINDER LABORATOIRE DE MATHÉMATIQUES Année scolaire 2021 → 2022

Fonctions de référence & algorithmes


Objectifs
En langage Python, des fonctions natives permettent de calculer efficacement des images de fonctions comme :
les puissances, la racine carrée, le logarithme népérien, l’exponentielle 1 .
• Comment proposer des algorithmes plus ou moins naïfs pour programmer ces fonctions ?
• Ces algorithmes sont-ils aussi « efficaces » que ceux programmés en Python, par exemple en terme de temps
de calcul ou en terme de précision des valeurs approchées obtenues ?

I. Puissances entières
I.1. Définition, calcul naïf
⟐ Définition 1
• Pour un nombre réel 𝑥 et pour un nombre entier naturel 𝑛 > 0, la puissance 𝑛-ème de 𝑥 est le nombre
noté 𝑥𝑛 et défini par le produit de 𝑥 par lui-même 𝑛 fois : 𝑥𝑛 = ⏟⏟⏟⏟⏟⏟⏟
𝑥 × ⋯ × 𝑥.
𝑛 facteurs
• Si 𝑛 = 0, on aura pour 𝑥 ≠ 0, 𝑥 = 1. Par convention, on pose souvent 00 = 1.
0

1
• Enfin 𝑥−𝑛 n’est défini que si 𝑥 ≠ 0 et est l’inverse de la puissance 𝑛-ème de 𝑥 : 𝑥−𝑛 = .
𝑥𝑛

‡ Proposition 1 : relation de récurrence d’une puissance


Pour un nombre réel 𝑥 et pour un nombre entier naturel 𝑛 ⩾ 1, 𝑥𝑛 = 𝑥 × 𝑥𝑛−1

⊛ Illustration 1
En utilisant cette méthode, on calcule les puissances de proche en proche.
Étapes 20 21 22 23 24 25 26 27

Calculs 1 2 × 1 = 2 2 × 2 = 4 2 × 4 = 8 2 × 8 = 16 2 × 16 = 32 2 × 32 = 64 2 × 64 = 128
On a donc calculé 21 → 22 → 23 → 23 → 24 → 25 → 26 → 27 .

Algorithme naïf de puissances :


def puiss_n(x, n):
assert type(n) == int and type(x) == float or int
if n == 0:
Commentaires
return 1
Le programme est composé de trois blocs d’instructions
y = 1
conditionnelles. Dans le corps d’une fonction écrite en Py-
if n > 0:
thon, on peut se contenter de n’utiliser que le mot-clef if et
for _ in range(n):
délaisser elif et else.
y = y*x
return y 1. Bloc 1 : cas 𝑥0 , renvoie 1.
if n < 0: 2. Bloc 2 : cas exposant positif : on répète n fois la multi-
for _ in range(abs(n)): plication par x. La variable de boucle n’étant pas utilisée
y = y*x pour les calculs, on utilise l’accumulateur _.
return 1/y
3. Bloc 3 : cas exposant négatif : on calcule la puissance
sans tenir compte du signe de l’exposant en faisant
comme dans le Bloc 2 et on prend l’inverse en fin de bloc.

1. Signalons le site rosetta code qui propose quelques algorithmes

P. FLAMBARD Page 1 sur 12 Réalisé grâce à LuaLATEX


Lycée Max LINDER LABORATOIRE DE MATHÉMATIQUES Année scolaire 2021 → 2022

I.2. Puissances (im)paires


‡ Proposition 2
Pour un nombre réel 𝑥 et pour un nombre entier naturel 𝑛 > 0, 𝑥2𝑛 = 𝑥𝑛 × 𝑥𝑛 .

⊛ Illustration 2
On peut se servir de cette propriété pour limiter le nombre de puissances calculées :
2
27 = 2 × 26 = 2 × (23 )
2 2 2
27 = 2 × (2 × 22 ) = 2 × (2 × (21 ) ) .
Au lieu de calculer toutes les puissances 20 ; ⋯ ; ⋯ ; 26 pour obtenir 27 , on va calculer
21 → 22 → 23 → 26 → 27 .
Algorithme récursif de puissances :
def puiss_r(x, n):
assert type(n) == int and type(x) == float or int
if n == 0: Commentaires
return 1
if n > 0: 1. Bloc conditionnel 2 : cas exposant positif :
if n%2 == 0: (a) si l’exposant est pair, on le « coupe en deux » :
y = puiss_r(x, n/2) 26 = 23 × 23 ;
return y*y
return x*puiss_r(x, n-1) (b) sinon, il est impair et on isole juste un facteur pour
return 1/puiss_r(x, -n) avoir un autre facteur d’exposant pair : 27 = 2 × 26 .
2. La dernière ligne traite le cas des exposants négatifs.
Remarques sur ce deuxième algorithme
• L’algorithme est dit récursif car la sortie de la fonction fait appel à la fonction elle-même (par exemple :
return x*puiss_r(x, n-1)).
• Le gain en nombres de multiplications à effectuer repose sur if n%2 == 0:
la partie : y = puiss_r(x, n/2)
Cette stratégie est appelée « diviser pour régner ». return y*y

I.3. Comparaison d’efficacité


Python permet de calculer les puissances d’un nombre par deux fonc-
tions : x**n et pow(x, n).
import time
On compare les temps de réalisation de calcul de 12 34556 789 par les t = time.time()
12345**56789
deux fonctions natives de Python et les deux fonctions proposées pré-
t1 = time.time() - t
cédemment à l’aide du code ci-contre. Une sortie obtenue est affichée t = time.time()
à des fins d’illustration (ces temps varient à chaque exécution et il faut pow(12345, 56789)
vérifier que les temps ne changent pas significativement en fonction de t1p = time.time() - t
l’ordre d’exécution !). t = time.time()
puiss_n(12345, 56789)
t2 = time.time() - t
On observe que l’algorithme naïf est le plus long à être réalisé. t = time.time()
puiss_r(12345, 56789)
x**n et pow(x, n) ont des temps d’exécution similaires, avec un petit t3 = time.time() - t
avantage pour l’objet intégré pow, qui lui-même semble être proche de print(t1, t1p, t2, t3)
l’algorithme récursif. 0.08051443099975586
0.07901406288146973
1.300227403640747
Pour la comparaison on s’est limité aux nombres entiers, le calcul des
0.07901358604431152
nombres flottants étant trop limité pour effectuer des tests : en Python,
les valeurs au-delà de 10309 renvoie inf ou une OverflowError.

P. FLAMBARD Page 2 sur 12 Réalisé grâce à LuaLATEX


Lycée Max LINDER LABORATOIRE DE MATHÉMATIQUES Année scolaire 2021 → 2022

II. Racine carrée


II.1. Définition
⟐ Définition 2
Pour un nombre réel 𝑥 positif, la racine carrée de 𝑥 est l’unique solution positive de l’équation d’inconnue
𝑟 : 𝑟2 = 𝑥.
‡ Proposition 3
Hormis les carrés parfaits : √4 = 2, √9 = 3, ..., la racine carrée d’un nombre entier positif (qui n’est
pas un carré parfait) est irrationnel : on ne peut pas obtenir de valeur exacte avec les quatre opérations de
base (addition, soustraction, multiplication, division) sur les nombres entiers.

II.2. Intérêt historique

La détermination de valeurs approchées de racine carrée est un


problème qui a été étudié avec divers outils et diverses époques.

La plus ancienne preuve d’une détermination est la tablette


YBC 7289 (estimée entre 1800 et 1600 avant notre ère) et qui
donne une valeur approchée de √2 correcte à 10−5 près.

Les égyptiens ont semble-t-il aussi disposés d’algorithmes pour


réaliser ces calculs. La méthode décrite ensuite, appelée méthode Tablette YBC 7289 par Bill Casselman
d’Héron (Ier siècle de notre ère) a sûrement été utilisée auparavant. √2 = 1 + 24 + 512 + 103 ≈ 1,414 212.
60 60 60
II.3. Méthode d’Héron
⊛ Illustration 3 : estimation de la racine carrée de 2
Héron semble avoir vu cette approche géométrique : partant d’un rectangle de dimension 2 × 1 et donc
d’aire 2, comment le transformer pour qu’il devienne de plus en plus proche d’un carré ?
• Il construit des rectangles d’aire 2 : en notant 𝑟 sa largeur et ℓ sa hauteur, on a donc 𝑟ℓ = 2 ⇔ ℓ = 2𝑟 .
• À partir du rectangle précédent, en faisant la moyenne arithmétique de ses dimensions, il observe que
le rectangle ainsi construit devient de plus en plus carré : or le carré d’aire 2 a un côté mesurant √2.
𝑟+ℓ 𝑟+ 2𝑟 1 2
Pour une ancienne largeur 𝑟, la nouvelle largeur sera donc 2
= 2
= 2 (𝑟 + 𝑟 ).

Étape 21 3 4 5
3 17 577 665 857
Largeur exacte 𝑟 2
2 12 408 470 832
Valeur approchée de 𝑟 2 1,5 1,4166... 1,414 215 686 274 5099... 1,414 213 562 374 6899...
886 731 088 897
L’étape 6 donnerait 627 013 566 048
≈ 1,414 213 562 373 095 1454.
Pour comparer : √2 ≈ 1,414 213 562 373 095 0488. Python donne : sqrt(2) : 1.4142135623730951

P. FLAMBARD Page 3 sur 12 Réalisé grâce à LuaLATEX


Lycée Max LINDER LABORATOIRE DE MATHÉMATIQUES Année scolaire 2021 → 2022

II.4. Algorithme pour la racine carrée de 2


La méthode d’Héron donne plusieurs points de satisfaction :
• on obtient des valeurs approchées sous forme de nombres rationnels (quotient de deux nombres entiers) ;
• en valeur approchée décimale, les décimales se stabilisent : l’étape 5 permet de conforter 1,414 21 comme
valeur approchée à 10−5 près ;
• l’étape 6 permet de conforter 1,414 213 562 37 comme valeur approchée à 10−11 près : la précision semble
doubler à chaque étape.
Cela pose néanmoins la question : combien d’étapes pour s’arrêter ?
Pour un calcul sur ordinateur, il semble prudent de s’arrêter une fois la valeur obtenue à 10−15 près (problème
expliqué plus tard). Plutôt qu’un nombre d’étapes, on fixe une condition d’arrêt : quand les deux dernières
valeurs approchées diffèrent à moins de 10−15 .
Commentaires
def rac2():
r, s = 2, 1 La variable s sert à stocker le résultat de l’étape précédente.
while abs(r-s) >= 1e-15: Quant à la variable r, elle est remplacée par la moyenne lar-
s = r geur/hauteur de la méthode de Héron.
r = 1/2*(r + 2/r)
return r La condition de la boucle while renvoie False quand les
variables r et s sont similaires à 10−16 près.
II.5. Algorithme pour la racine carrée d’un nombre réel positif

def rac(x):
assert x >= 0
L’idée reste la même : pour un rectangle de largeur 𝑟, si son
r, s = x, 1
aire vaut 𝑥, alors sa largeur vaut 𝑥𝑟 . La prochaine largeur sera
while abs(r-s) >= 1e-15:
s = r donc la moyenne 12 (𝑟 + 𝑥𝑟 ).
r = 1/2*(r + x/r)
return r

II.6. Approche analytique


‡ Proposition 4
𝑟0 = 𝑎 ⩾ 0
La suite définie par 1 𝑎 est bien définie, décroissante et converge vers √𝑥.
{𝑟𝑛+1 = 2 ( 𝑟𝑛 + 𝑟𝑛 )

1 𝑎
La démonstration repose sur la converge monotone, la fonction ϕ𝑎 ∶ 𝑥 ↦ 2 (𝑥 + 𝑥 ) étant croissante sur
[√𝑎 ; 𝑎], on montre par récurrence que pour tout nombre entier naturel 𝑛 : √𝑎 ⩽ 𝑟𝑛 ⩽ 𝑟𝑛+1 ⩽ 𝑎.
‡ Proposition 5
𝑟0 = 𝑎
La vitesse de convergence de la suite 1 𝑎 est quadratique : pour tout nombre entier
{𝑟𝑛+1 = 2 (𝑟𝑛 + 𝑟𝑛 )
naturel 𝑛, |𝑟𝑛 − √𝑎| ⩽ 21𝑛 |𝑎 − √𝑎|.
Par conséquence, la précision de la valeur approchée est bien doublée à chaque nouveau terme.
1
La démonstration repose sur le fait que la fonction ϕ𝑎 est contractante : comme |ϕ′𝑎 (𝑥)| ⩽ 2
pour 𝑥 > 0, pour
1
𝑎 > 0 et 𝑏 > 0, |𝑓 (𝑏) − 𝑓 (𝑎)| ⩽ 2
|𝑏 − 𝑎|. Une récurrence donne alors l’inégalité attendue en remarquant que

|𝑟𝑛+1 − √𝑎| = |ϕ𝑎 (𝑟𝑛 ) − ϕ𝑎 (𝑎)| ⩽ 12 |𝑟𝑛 − √𝑎|.

P. FLAMBARD Page 4 sur 12 Réalisé grâce à LuaLATEX


Lycée Max LINDER LABORATOIRE DE MATHÉMATIQUES Année scolaire 2021 → 2022

III. Logarithme népérien


III.1. Définition
⟐ Définition 3
La fonction logarithme népérien ou logarithme naturel, notée ln, est définie de manière équivalente :
• comme la primitive de la fonction inverse sur • comme la réciproque de la fonction exponentielle
]0 ; +∞[ s’annulant en 1 : (qui vérifie exp′ = exp et exp(0) = 1) :
′ 1
∀𝑥 > 0, ln (𝑥) = 𝑥 et ln(1) = 0 ; ∀𝑥 ∈ ℝ, ln(exp(𝑥)) = 𝑥 et ∀𝑥 > 0, exp(ln(𝑥)) = 𝑥.

Remarque : la notation originelle de la fonction l ou l. est devenue log ou Log. Une norme de 1961 a imposée
la notation ln : les premiers langages informatiques avaient néanmoins implémenté cette fonction avec log,
notation que l’on retrouve encore de nos jours. Pour ajouter à la confusion, log s’est imposé comme notation
ln(𝑥)
pour le logarithme décimal : log(𝑥) = ln(10) et qui est souvent implémenté avec la notation log10.
III.2. Intérêt historique des logarithmes
‡ Proposition 6 : relation fonctionnelle de ln
Pour tous nombres réels 𝑎 > 0 et 𝑏 > 0, ln(𝑎𝑏) = ln(𝑎) + ln(𝑏).
On en tire les relations ln ( 𝑎𝑏 ) = ln(𝑎) − ln(𝑏) et ln (𝑎𝑏 ) = 𝑏 ln(𝑎).

Apparu au milieu XVIIe siècle, les logarithmes ont permis un gain en terme d’efficience en calculs.
⊛ Illustration 4
En disposant d’abaques de logarithmes (qui donnent des valeurs de ln(𝑎) en fonction de 𝑎), on peut sim-
plifier certains calculs fastidieux.
• Pour réaliser le produit 1234 × 5678 :
∗ On cherche dans l’abaque les logarithmes des deux facteurs :
ln(1234) ≈ 7,118 016 et log(5678) ≈ 8,644 354.
∗ Comme ln(1234 × 5678) = ln(1234) + ln(5678), on fait la somme des logarithmes :
7,118 016 + 8,644 354 = 15,762 370.
∗ On cherche la valeur dans le logarithme vaut 15,762 370 : on trouve 7 006 648.
∗ On peut comparer l’erreur commise : 1234 × 5678 = 7 006 652 soit une erreur relative de 5 × 10−7 .
1
• Pour réaliser l’extraction 5678 9 :
∗ On cherche dans l’abaque le logarithmes de l’exposé : log(5678) ≈ 8,644 354.
1
1
∗ Comme ln (5678 9 ) = 9
ln(5678), on effectue la division 8,644 354 ÷ 9 ≈ 0,960 48378.
∗ On cherche la valeur dans le logarithme vaut 0,960 48378 : on trouve 2,612 960.
1
∗ On peut comparer l’erreur commise : 5678 9 ≈ 2,612 960, donc conforme à 10−6 près.

III.3. Approximation de ln, algorithme de Briggs


‡ Proposition 7 : approximation de ln(1 + 𝑥)
L’équation réduite de la tangente au point d’abscisse 1 de 𝒞ln est 𝑦 = 𝑥 − 1.
On a donc l’approximation affine pour 𝑥 ≈ 1 : ln(𝑥) ≈ 𝑥 − 1.

⊛ Illustration 5
Cette approximation n’est pertinente que sur un intervalle d’amplitude limité autour de 1.
• ln(1,01) ≈ 0,009 950 vs 1,01 − 1 = 0,01 : erreur relative de 5 × 10−3 .
• ln(1,000 000 005 164 349) ≈ 0,000 000 005 164 349 vs 1,000 000 005 164 349 − 1 = 0,000 000 005 164 349.

P. FLAMBARD Page 5 sur 12 Réalisé grâce à LuaLATEX


Lycée Max LINDER LABORATOIRE DE MATHÉMATIQUES Année scolaire 2021 → 2022

Commentaires :
Algorithme de Briggs
L’idée de l’algorithme est de tirer profit de l’approximation
𝑥 ≈ 1 ⇒ ln(𝑥) ≈ 𝑥 − 1.
from math import sqrt
def logn(x): On calcule n fois la racine carrée de 𝑥 : cette succession
assert x > 0 de racines carrées assure de trouver une valeur proche de 1.
r = x C’est l’objet de la boucle conditionnelle.
n = 0
while abs(r-1) > 1e-8: À l’issue de cette boucle, r est assez proche de 1 pour que
r = sqrt(r) son logarithme soit calculé par r-1 dans la sortie.
n = n + 1
return (r-1) * 2**n L’identité ln (√𝑥) = 12 ln(𝑥) implique que les n calculs de
racine carrée reviennent à diviser le logarithme par 2𝑛 : on
compense en multipliant par 2𝑛 dans la sortie.
III.3.1. Comparaison d’efficacité
En important la fonction log, on teste la validité de cet algorithme.
for k in range(10, 101, 10):
print(log(k), logn(k), abs(log(k)-logn(k)))
2.302585092994046 2.3025850653648376 2.762920825460924e-08
2.995732273553991 2.995732307434082 3.388009117699653e-08
3.4011973816621555 3.4011974334716797 5.1809524226342774e-08
3.6888794541139363 3.6888794898986816 3.578474538912246e-08
3.912023005428146 3.9120230674743652 6.204621927352605e-08
4.0943445622221 4.09434449672699 6.549511066822333e-08
4.248495242049359 4.2484952211380005 2.091135886672646e-08
4.382026634673881 4.382026672363281 3.7689400045337607e-08
4.499809670330265 4.499809622764587 4.756567761887709e-08
4.605170185988092 4.605170130729675 5.525841650921848e-08

Les meilleures approximations (ici de l’ordre de 10−8 par rapport à la fonction pythonesque) sont obtenues avec
la condition de boucle abs(r-1) > 10**-8.
Piste d’explication : si r est « trop » proche de 1, la perte en chiffres significatifs de r-1 entraîne les erreurs.
III.3.2. Approche analytique
On est assuré que la condition de boucle renverra False en un nombre fini d’étapes par la proposition suivante.
‡ Proposition 8
𝑢0 = 𝑥 > 0
La suite définie par converge de manière monotone vers 1 (décroissante si 𝑢0 > 1,
{𝑢𝑛+1 = √𝑢𝑛
croissante si 0 < 𝑢0 < 1).

La fonction 𝑥 ↦ √𝑥 est croissante sur [0 ; +∞[ et 0 < 𝑥 < 1 ⇒ 𝑥 ⩽ √𝑥 alors que 𝑥 > 1 ⇒ √𝑥 ⩽ 𝑥.
Une récurrence montre alors le caractère monotone et borné par le premier terme et 1. La convergence monotone
permet alors de conclure. Quant à la limite, comme elle vérifie ℓ = √ℓ, ℓ = 1.

Les algorithmes, les abaques de racine carrée étant connues et utilisées à l’époque de Napier et Briggs font que
la mise en œuvre de calculs itérés de racine carrée n’était pas un obstacle. Des abaques de logarithme ont donc
été « rapidement » mises au point.

P. FLAMBARD Page 6 sur 12 Réalisé grâce à LuaLATEX


Lycée Max LINDER LABORATOIRE DE MATHÉMATIQUES Année scolaire 2021 → 2022

III.4. Algorithme CORDIC


Observations préliminaires :
1. • Si 𝑥 > 10, alors 𝑥 = 𝑚 × 10𝑝 (écriture scientifique) et ln(𝑥) = ln(𝑚) + 𝑝 ln(10) où la mantisse 𝑚 ∈ [1 ; 10[.
• De même, si 0 < 𝑥 < 1, 𝑥 = 𝑚 × 10𝑝 (avec 𝑝 < 0) et ln(𝑥) = ln(𝑚) + 𝑝 ln(10).
Il suffit donc de connaître une approximation de ln(𝑚) pour 𝑚 ∈ [1 ; 10[.
2. Si 𝑚 ∈ [1 ; 10[, on peut trouver des valeurs entières 𝑘0 , …, 𝑘10 telles que
𝑚 = 2𝑘1 × 1,1𝑘2 × 1,01𝑘3 × ⋯ × 1,000 000 000 001𝑘15 , et alors
ln(𝑚) = 𝑘1 ln(2) + 𝑘2 ln(1,1) + 𝑘3 ln(1,01) + ⋯ + 𝑘15 ln(1,000 000 000 001)
L’algorithme CORDIC se propose donc :
• rentrer en mémoire des valeurs particulières de logarithme (on appelle cela la mémoire morte) ;
• décomposer l’entrée afin de calculer son logarithme à l’aide des valeurs particulières de logarithme.

def logC(x):
assert x > 0
memm = [2.302585092994046, 0.6931471805599453, 0.09531017980432493,
0.009950330853168092, 0.0009995003330834232, 9.999500033329732e-05,
9.999950000398841e-06, 9.999994999180668e-07, 9.999999505838704e-08,
9.999999889225291e-09, 1.000000082240371e-09, 1.000000082690371e-10,
1.0000000827353709e-11, 1.000088900581841e-12, 9.992007221625909e-14,
9.992007221626358e-15, 1.110223024625156e-15]
p, xx = 0, x
if x < 1: Commentaires
return -logC(1/x)
while xx > 10: Le programme est composé d’un bloc conditionnel, d’une
xx = xx / 10 boucle conditionnelle et d’une boucle d’itération.
p = p + 1 1. Bloc conditionnel : ramène le calcul de ln(𝑥) pour 𝑥 < 1 à
y = (p+1) * memm[0] − ln ( 𝑥1 ) (meilleure précision).
for k in range(16):
z = 1 + 10**-k 2. Boucle conditionnelle : détermine la valeur p de l’exposant
while xx*z <= 10: dans la notation scientifique 𝑚 × 10𝑝 .
xx = xx * z 3. Bloc itératif : pour chaque valeur 𝑘, on cherche à trouver :
y = y - memm[k+1] • le plus petit entier 𝑘 tel que 2𝑘 ⩽ 10
𝑥
⩽ 2𝑘+1 ;
return y
• puis le plus petit entier 𝑘 tel que 1,1𝑘 ⩽ 10
𝑘1 ⩽ 1,1
𝑘+1
;
2 𝑥
• et ainsi de suite.
4. Le logarithme cherché se détermine par combinaison li-
néaire des valeurs 𝑘 associées aux logarithmes de la mé-
moire morte.
III.5. Comparaison d’efficacité

for k in range(10, 51, 10): Les approximations sont bien


print(log(k), logC(k), abs(log(k)-logC(k))) meilleures qu’avec l’algorithme
2.302585092994046 2.302585092994046 0.0 de Briggs : on a contourné le pro-
2.995732273553991 2.9957322735539913 4.440892098500626e-16 blème de la limite de la préci-
3.4011973816621555 3.401197381662156 4.440892098500626e-16 sion en rentrant assez de valeurs
3.6888794541139363 3.688879454113936 4.440892098500626e-16 de logarithmes dans la mémoire
3.912023005428146 3.9120230054281464 4.440892098500626e- morte.
16

P. FLAMBARD Page 7 sur 12 Réalisé grâce à LuaLATEX


Lycée Max LINDER LABORATOIRE DE MATHÉMATIQUES Année scolaire 2021 → 2022

IV. Exponentielle
IV.1. Définition
⟐ Définition 4
𝑦′ = 𝑦
La fonction exponentielle est l’unique fonction 𝑦 solution du problème de Cauchy .
{𝑦(0) = 1

IV.2. Méthode d’Euler de résolution des systèmes différentiels


‡ Proposition 9 : formule de Taylor
𝑓 est une fonction définie et dérivable sur un intervalle I, 𝑎 un nombre réel.
Connaissant la fonction dérivée 𝑓 ′ de 𝑓 et l’image de 𝑎 par 𝑓, on peut estimer pour un nombre réel ℎ
« petit » (avec en particulier 𝑎 + ℎ ∈ I) : 𝑓 (𝑎 + ℎ) ≈ 𝑓 (𝑎) + ℎ ⋅ 𝑓 ′ (𝑎).

‡ Proposition 10
On peut évaluer pour ℎ « petit » : exp(ℎ) ≈ (1 + ℎ).
Pour une valeur 𝑥, et pour ℎ fixé, on a avec 𝑛 = ⌊ ℎ𝑥 ⌋, exp(𝑥) ≈ (1 + ℎ)𝑛 .

def expE(x, h=1e-5): Commentaires


if x < 0:
return expE(-x, h) Cet algorithme ne sera pas efficace :
e, a = 1., x
• si ℎ est « trop grand », l’approximation sera mauvaise dès que
while abs(a) > h:
|𝑥| sera modérément élevé ;
e = (1+h) * e
a = a - h • si ℎ est « trop petit », la quantité de calcul en fera une approxi-
return e mation inutilisable en pratique.

IV.3. Série exponentielle


‡ Proposition 11 +∞
𝑥𝑛
On peut prouver que pour tout nombre réel 𝑥 : exp(𝑥) = ∑ .
𝑛=0
𝑛!
Ainsi exp(𝑥) s’obtient en faisant la somme des termes d’une suite (𝑢𝑛 ) vérifiant la relation de récurrence
𝑥
𝑢𝑛+1 = 𝑢𝑛 × 𝑛+1 .
Commentaires

• Bloc conditionnel 1 : vu que e−745 ≈ 10−325 , il sera


interprété comme 0.0 par Python.
def expn(x):
if x < -745: • Bloc conditionnel 2 : l’approximation est meilleure en
return 0.0 évaluant des valeurs de e𝑥 pour 𝑥 > 0, ainsi pour 𝑥 <
if x < 0: 0 ; on utilise l’identité e−𝑥 = e1𝑥 .
return 1/expn(-x) • Boucle conditionnelle : la somme s’arrête dès que
e, t, n = 1+x, x, 1 l’une des conditions est satisfaite :
while abs(t)>1e-15 and n<1e3: ∗ le terme de la somme est inférieure à 10−15 , et donc
n = n + 1 ne contribuera plus significativement à la mantisse
t = t*x/n du résultat ;
e = e + t
return e ∗ quand le 1000e terme de la somme est calculé : sans
cela, la condition n’est pas atteinte en un temps rai-
sonnable pour des valeurs de 𝑥 plus élevées.
• La fonction renvoie inf dès que 𝑥 > 709
(car alors e𝑥 > 10308 ).

P. FLAMBARD Page 8 sur 12 Réalisé grâce à LuaLATEX


Lycée Max LINDER LABORATOIRE DE MATHÉMATIQUES Année scolaire 2021 → 2022

IV.4. Comparaison d’efficacité

from math import exp


for k in range(10, 101, 10):
print(exp(k), expn(k), abs((expn(k)-exp(k))/exp(k)))
22026.465794806718 22026.46579480671 3.3032796463874436e-16
485165195.4097903 485165195.4097902 1.2285432949295985e-16
10686474581524.463 10686474581524.467 3.655321472203193e-16
2.3538526683702e+17 2.3538526683701997e+17 1.3594733616933084e-16
5.184705528587072e+21 5.184705528587075e+21 6.06732240173584e-16
1.1420073898156842e+26 1.1420073898156847e+26 4.513071282342429e-16
2.515438670919167e+30 2.5154386709191652e+30 6.71393773097562e-16
5.54062238439351e+34 5.540622384393506e+34 6.658726328532775e-16
1.2204032943178408e+39 1.2204032943178407e+39 1.238244178423794e-16
2.6881171418161356e+43 2.6881171418161336e+43 7.368369599839732e-16

En terme d’erreur relative, la version proposée ne diffère pas significativement de la fonction native de Python
(erreur proche de 10−16 ).
L’algorithme natif de Python est plus performant :
• pour une valeur de l’intervalle [709 ; 709,8[ où il donne une valeur (au-delà, il donnera aussi inf) ;
• pour une valeur de l’intervalle [−745 ; −709[ où il donne des valeurs non nulles.
V. Factorielle
V.1. Définition, calcul naïf
⟐ Définition 5
Pour un nombre entier naturel 𝑛 > 0, la factorielle de 𝑛 est le nombre noté 𝑛! et est le produit des nombres
entiers positifs de 1 à 𝑛 : 𝑛! = 1 × ⋯ × 𝑛.
Par convention, on pose 0! = 1.

‡ Proposition 12
Pour tout nombre entier naturel 𝑛 > 0, (𝑛 + 1)! = (𝑛 + 1) × 𝑛!.

Algorithme naïf de factorielle


def fact_b(n):
if n in [0, 1]:
Commentaires :
return 1
Le code traite à part les cas 𝑛 ∈ {0 ; 1}.
f = 1
Le calcul repose sur la relation de récurrence
for k in range(2, n+1):
(𝑛 + 1)! = (𝑛 + 1) × 𝑛!.
f = f*k
return f

V.2. Programme récursif (et son intérêt pédagogique)


On trouve régulièrement le programme pour calculer 𝑛! sous forme d’une fonction récursive pour illustrer (avec
un exemple court) le principe de programme récursif.
Commentaires :
def fact_r(n): En terme de performances, le programme est catastro-
if n in [0, 1]: phique :
return 1 • la récursivité ne modifie pas le principe du calcul
f = 1 précédent : faire les 𝑛 multiplications ;
return n * fact_r(n-1) • pire, il encombre la mémoire pour exécuter 𝑛 − 1
fonctions.

P. FLAMBARD Page 9 sur 12 Réalisé grâce à LuaLATEX


Lycée Max LINDER LABORATOIRE DE MATHÉMATIQUES Année scolaire 2021 → 2022

V.3. Programme reposant sur la formule de Legendre


Une autre approche pour la calcul de 𝑛! :
• on détermine tous les nombres premiers 𝑝 divisant 𝑛! ;
• pour chaque nombre premier 𝑝 divisant 𝑛!, on détermine l’exposant de 𝑝 dans la décomposition en nombres
premiers de 𝑛! à l’aide de la formule de Legendre.

⊛ Illustration 6
12! = 2 × 3 × 4 × 5 × 6 × 7 × 8 × 9 × 10 × 11 × 12
12! = 210 × 35 × 52 × 7 × 11

‡ Proposition 13 : formule de Legendre +∞


𝑛!
L’exposant de 𝑝 dans la décomposition en nombres premiers de 𝑛! est déterminé par : 𝑣𝑝 (𝑛!) = ∑ .
⌊ 𝑝𝑘 ⌋
𝑘=1

Algorithme avec formule de Legendre


from math import sqrt
def facto_L(n):
if n in [0, 1]:
return 1
lp = []
if n%2 == 0:
lp.append(2) Commentaires :
for d in range(3, n+1, 2):
b = True
a = pow(2, d-1, d) • Le premier bloc conditionnel traite les cas 0!
if a == 1: et 1!.
i, r = 3, sqrt(d) • La liste lp va stocker tous les nombres pre-
while i <= r: miers divisant 𝑛! : un test de Fermat avec
if d%i == 0: 𝑎 = 2 puis le test classique.
b = False • Pour déterminer les diviseurs premiers de 𝑛!,
break un test de Fermat du diviseur avec l’évalua-
i = i + 2 tion de la puissance modulaire 2𝑑−1 (mod 𝑑)
if a != 1: et en cas de réussite, un test plus classique du
b = False caractère premier du diviseur.
if b:
• La liste vp va stocker les exposants associés
lp.append(d)
aux nombres premiers en faisant la somme
vp = []
n//p**k tant que n>p**k : c’est la mise en
for p in lp:
œuvre de la formule de Legendre.
v, q = 0, p
while q <= n: • Le calcul de la factorielle se fait par produit
v = v + n//q des 𝑝𝑣𝑝 (𝑛!) .
q = q*p
vp.append(v)
f = 1
for k in range(len(lp)):
f = f*pow(lp[k], vp[k])
return f

P. FLAMBARD Page 10 sur 12 Réalisé grâce à LuaLATEX


Lycée Max LINDER LABORATOIRE DE MATHÉMATIQUES Année scolaire 2021 → 2022

V.4. Comparaison d’efficacité

from math import factorial


import time

t = time.time() La fonction native de Python est beaucoup plus efficace que


factorial(60000) les algorithmes proposés (facteur 10 pour le calcul naïf et 4
t1 = time.time() - t avec la formule de Legendre pour 60 000!).
t = time.time()
fact_b(60000) La fonction native repose sur des méthodes générales, no-
t2 = time.time() - t tamment par scindage binaire a .
t = time.time() a. On peut voir sur hal-01431717, chap.16 pour approfondir.
facto_L(60000)
t4 = time.time() - t
print(t1, t2, t4)
0.42327117919921875 4.255580902099609 1.6407227516174316
VI. Quelques éléments sur l’implémentation de valeurs numériques
VI.1. Stockage des informations sur un ordinateur
Les ordinateurs sont des systèmes de traitement de l’information 2 .
Leur fonctionnement nécessite en particulier :
• de stocker des informations, c’est-à-dire de disposer d’une manière de traduire des données sous une forme
physique ;
• de lire des informations, c’est-à-dire de disposer d’une manière de faire la traduction dans le sens inverse.

Ces questions recouvrent beaucoup de questionnements et de difficultés (fiabilité, sécurité, efficacité, ...).

Si les ordinateurs quantiques prévoient de modifier certains des paradigmes suivants, les technologies actuelles
reposent sur les principes suivants :
• il existe une unité de base de stockage, appelée bit, qui n’existe qu’en deux états (conventionnellement
notés 0 et 1) ;
• à l’état physique, un bit correspond à un dispositif reposant sur le passage du courant électrique :
∗ lors de la lecture de l’état, si le courant ne passe pas, c’est l’état 0, sinon, c’est l’état 1 ;
∗ sur une unité de stockage, on peut faire passer le dispositif unitaire d’un état à un autre (en général, par
envoi d’une tension importante).

On retrouve plusieurs dispositifs reposant sur ce paradigme :


• les CD/DVD disposaient sur leur face de lecture de creux et de bosses ;
• les QR-code disposent de zones claires et de zones foncées ;
• les anciens disques durs étaient composés de plateaux, découpés en secteurs dont on pouvait changer le
champ magnétique local.

La technologie mémoire flash 3 , la plus répandue actuellement, repose sur une grille flottante contenue dans
l’oxyde de grille. Une tête de lecture/écriture permet de mesurer la tension (lecture) mais aussi de piéger/dé-
piéger les électrons (écriture) par effet tunnel par exemple.
2. Toute donnée, réelle ou abstraite, manipulable par la pensée humaine (Claude SHANNON).
3. Quelques schémas et compléments éclairants à consulter sur wikipedia.

P. FLAMBARD Page 11 sur 12 Réalisé grâce à LuaLATEX


Lycée Max LINDER LABORATOIRE DE MATHÉMATIQUES Année scolaire 2021 → 2022

VI.2. Nombres entiers


Pour stocker un nombre entier, la convention suivante est adoptée :
• le premier bit, dit bit de signe, vaut 0 pour un nombre positif, sert à stocker le signe ;
• les bits suivants vont stocker l’écriture binaire du nombre.
Ainsi 5241 sera stocké : 01010001111001.
ln(𝑥)
Ainsi, si l’écriture décimale d’un nombre entier 𝑛 utilise 1 + lb(|𝑛|) chiffres en écriture binaire, où lb(𝑥) = log(2)
est le logarithme binaire.
Certains langages de programmation font le choix de limiter le nombre de bits pour implémenter un nombre
entier (c’était le cas de Python 2.x qui avait un type entier long). Python 3.x ne limite pas. Chaque choix présente
ses avantages et ses inconvénients.
VI.3. Norme IEE754
Afin d’implémenter des valeurs non-entières d’ordre de grandeur variés, l’idée est de recourir à notation scien-
tifique. Un nombre écrit sous la forme scientifique est de la forme 𝑚 × 10𝑒 où 𝑚 est la mantisse et 𝑒 l’exposant.
Il suffit donc de stocker la mantisse et l’exposant.
La norme IEE754 4 permet de créer une convention pour faciliter l’interopérabilité : elle décrit l’implémentation
des nombres flottants.
On ne présente ici que le format double précision car les ordinateurs actuelles ne sont fournis qu’en 64 bits (la
larguer du registre du processeur est de 64 bits).
• le premier bit est le bit de signe de la mantisse ;
• les 11 bits suivants stockent l’exposant biaisé 5 ;
• les 52 bits suivant stockent la mantisse.
Le plus petit nombre différent de 0 représenté est ±2−1074 ≈ ±4,9 × 10−324 et le plus grand nombre représenté
est 21024 − 2971 ≈ 10308 .
Cela explique les affichages suivants :
>>> 1e-323 >>> 1e308
1e-323 1e308
>>> 1e-324 >>> 1e309
0.0 inf

VI.4. Précautions dans l’usage des flottants


1. Problèmes de représentation.
• Problème d’overflow/underflow avec les limites 10308 et 10−323 évoquées au-dessus.
• Des nombres s’écrivant de manière exacte en décimal ne s’écrivent pas de manière exacte en binaire/flot-
tant. Ainsi 0,1 en décimal s’écrirait en binaire : 0,000 101 000 101 000....
>>> .1 + .1 + .1
0.30000000000000004

2. Problèmes positionnel.
• On ne teste pas l’égalité de deux flottants, on préfèrera abs(f1-f2) > 1e-12 à f1 == f2.
• Le calcul peut être non associatif.
>>> 1e-100 + 1 - 1 >>> 1 - 1 + 1e-100
0.0 1e-100

4. Un wikihow avec des schémas pour compléter ces quelques explications.


5. Ce choix facilite la comparaison : pas de bit de signe mais 000 000 000 01 représente −2−10 et non +20 .

P. FLAMBARD Page 12 sur 12 Réalisé grâce à LuaLATEX

Vous aimerez peut-être aussi