From 6a00e7456e1cd1e40025e2a71112eca96299b918 Mon Sep 17 00:00:00 2001 From: Orestis <orestis.malaspinas@pm.me> Date: Mon, 30 Oct 2023 08:58:21 +0100 Subject: [PATCH] maj 2023 --- slides/cours_5.md | 471 ----------------------- slides/cours_6.md | 946 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 946 insertions(+), 471 deletions(-) create mode 100644 slides/cours_6.md diff --git a/slides/cours_5.md b/slides/cours_5.md index 22b77c6..151a338 100644 --- a/slides/cours_5.md +++ b/slides/cours_5.md @@ -483,474 +483,3 @@ $$ * Comportement indéfini! -# Nombres à virgule (1/3) - -## Comment manipuler des nombres à virgule? - -$$ -0.1 + 0.2 = 0.3. -$$ - -Facile non? - -. . . - -## Et ça? - -```C -#include <stdio.h> -#include <stdlib.h> -int main(int argc, char *argv[]) { - float a = atof(argv[1]); - float b = atof(argv[2]); - printf("%.10f\n", (double)(a + b)); -} -``` - -. . . - -## Que se passe-t-il donc? - -# Nombres à virgule (2/3) - -## Nombres à virgule fixe - -+-------+-------+-------+-------+-----+----------+----------+----------+----------+ -| $2^3$ | $2^2$ | $2^1$ | $2^0$ | `.` | $2^{-1}$ | $2^{-2}$ | $2^{-3}$ | $2^{-4}$ | -+-------+-------+-------+-------+-----+----------+----------+----------+----------+ -| `1` | `0` | `1` | `0` | `.` | `0` | `1` | `0` | `1` | -+-------+-------+-------+-------+-----+----------+----------+----------+----------+ - -## Qu'est-ce ça donne en décimal? - -. . . - -$$ -2^3+2^1+\frac{1}{2^2}+\frac{1}{2^4} = 8+2+0.5+0.0625=10.5625. -$$ - -## Limites de cette représentation? - -. . . - - -* Tous les nombres `> 16`. -* Tous les nombres `< 0.0625`. -* Tous les nombres dont la décimale est pas un multiple de `0.0625`. - -# Nombres à virgule (3/3) - -## Nombres à virgule fixe - -* Nombres de $0=0000.0000$ à $15.9375=1111.1111$. -* Beaucoup de "trous" (au moins $0.0625$) entre deux nombres. - -## Solution partielle? - -. . . - -* Rajouter des bits. -* Bouger la virgule. - -# Nombres à virgule flottante (1/2) - -## Notation scientifique - -* Les nombres sont représentés en terme: - * Une mantisse - * Une base - * Un exposant - -$$ -\underbrace{22.1214}_{\mbox{nombre}}=\underbrace{221214}_{\mbox{mantisse}}\cdot -{\underbrace{10}_{\mbox{base}}}{\overbrace{^{-4}}^{\mbox{exp.}}}, -$$ - -. . . - -On peut donc séparer la représentation en 2: - -* La mantisse -* L'exposant - -# Nombres à virgule flottante (2/2) - -## Quel est l'avantage? - -. . . - -On peut représenter des nombres sur énormément d'ordres de grandeur avec un -nombre de bits fixés. - -## Différence fondamentale avec la virgule fixe? - -. . . - -La précision des nombres est **variable**: - -* On a uniquement un nombre de chiffres **significatifs**. -$$ -123456\cdot 10^{23}+ 123456\cdot 10^{-23}. -$$ - -## Quel inconvénient y a-t-il? - -. . . - -Ce mélange d'échelles entraîne un **perte de précision**. - -# Nombres à virgule flottante simple précision (1/4) - -Aussi appelés *IEEE 754 single-precision binary floating point*. - -](figs/Float_example_bare.svg) - -## Spécification - -* 1 bit de signe, -* 8 bits d'exposant, -* 23 bits de mantisse. - -$$ -(-1)^{b_{31}}\cdot 2^{(b_{30}b_{29}\dots b_{23})_{2}-127}\cdot (1.b_{22}b_{21}\dots b_{0})_{2}, -$$ - -## Calculer la valeur décimale du nombre ci-dessus - -# Nombres à virgule flottante simple précision (2/4) - -](figs/Float_example.svg) - -. . . - -\begin{align} -\mbox{exposant}&=\sum_{i=0}^7 b_{23+i}2^i=2^2+2^3+2^4+2^5+2^6=124-127,\\ -\mbox{mantisse}&=1+\sum_{i=1}^{23}b_{23-i}2^{-i}=1+2^{-2}=1.25,\\ -&\Rightarrow (-1)^0\cdot 2^{-3}\cdot 1.25=0.15625 -\end{align} - -# Nombres à virgule flottante simple précision (3/4) - -## Quel nombre ne peux pas être vraiment représenté? - -. . . - -## Zéro: exception pour l'exposant - -* Si l'exposant est `00000000` (zéro) -$$ -(-1)^{\mbox{sign}}\cdot 2^{-126}\cdot 0.\mbox{mantisse}, -$$ -* Sinon si l'exposant est `00000001` à `11111110` -$$ -\mbox{valeur normale}, -$$ -* Sinon `11111111` donne `NaN`. - -# Nombres à virgule flottante simple précision (4/4) - -## Quels sont les plus petits/grands nombres positifs représentables? - -. . . - -\begin{align} -0\ 0\dots0\ 0\dots01&=2^{-126}\cdot 2^{-23}=1.4...\cdot -10^{-45},\\ -0\ 1\dots10\ 1\dots1&=2^{127}\cdot (2-2^{-23})=3.4...\cdot -10^{38}. -\end{align} - -## Combien de chiffres significatifs en décimal? - -. . . - -* 24 bits ($23 + 1$) sont utiles pour la mantisse, soit $2^{24}-1$: - * La mantisse fait $\sim2^{24}\sim 10^7$, - * Ou encore $\sim \log_{10}(2^{24})\sim 7$. -* Environ **sept** chiffres significatifs. - -# Nombres à virgule flottante double précision (64bits) - -## Spécification - -* 1 bit de signe, -* 11 bits d'exposant, -* 52 bits de mantisse. - -. . . - -## Combien de chiffres significatifs? - -* La mantisse fait $\sim 2^{53}\sim10^{16}$, -* Ou encore $\sim \log_{10}(2^{53})\sim 16$, -* Environ **seize** chiffres significatifs. - -## Plus petit/plus grand nombre représentable? - -. . . - -* Plus petite mantisse et exposant: $\sim 2^{-52}\cdot 2^{-1022}\sim 4\cdot 10^{-324}$, -* Plus grande mantisse et exposant: $\sim 2\cdot 2^{1023}\sim \cdot 1.8\cdot 10^{308}$. - -# Précision finie (1/3) - -## Erreur de représentation - -* Les nombres réels ont potentiellement un **nombre infini** de décimales - * $1/3=0.\overline{3}$, - * $\pi=3.1415926535...$. -* Les nombres à virgule flottante peuvent en représenter qu'un **nombre - fini**. - * $1/3\cong 0.33333$, erreur $0.00000\overline{3}$. - * $\pi\cong3.14159$, erreur $0.0000026535...$. - -On rencontre donc des **erreurs de représentation** ou **erreurs -d'arrondi**. - -. . . - -## Et quand on calcule? - -* Avec deux chiffres significatifs -\begin{align} -&8.9+(0.02+0.04)=8.96=9.0,\\ -&(8.9+0.02)+0.04=8.9+0.04=8.9. -\end{align} - -. . . - -## Même pas associatif! - -# Précision finie (2/3) - -## Erreur de représentation virgule flottante - -$$ -(1.2)_{10} = 1.\overline{0011}\cdot 2^0\Rightarrow 0\ 01111111\ -00110011001100110011010. -$$ -Erreur d'arrondi dans les deux derniers bits et tout ceux qui viennent -ensuite -$$ -\varepsilon_2 = (00000000000000000000011)_2. -$$ -Ou en décimal -$$ -\varepsilon_{10} = 4.76837158203125\cdot 10^{-8}. -$$ - -# Précision finie (3/3) - -## Comment définir l'égalité de 2 nombres à virgule flottante? - -. . . - -Ou en d'autres termes, pour quel $\varepsilon>0$ (appelé `epsilon-machine`) on a -$$ -1+\varepsilon = 1, -$$ -pour un nombre à virgule flottante? - -. . . - -Pour un `float` (32 bits) la différence est à -$$ -2^{-23}=1.19\cdot 10^{-7}, -$$ -Soit la précision de la mantisse. - -## Comment le mesurer (par groupe)? - -. . . - -```C -float eps = 1.0; -while ((float)1.0 + (float)0.5 * eps != (float)1.0) { - eps = (float)0.5 * eps; -} -printf("eps = %g\n", eps); -``` - -# Erreurs d'arrondi - -Et jusqu'ici on a encore pas fait d'arithmétique! - -## Multiplication avec deux chiffres significatifs, décimal - -$$ -(1.1)_{10}\cdot (1.1)_{10}=(1.21)_{10}=(1.2)_{10}. -$$ -En continuant ce petit jeu: -$$ -\underbrace{1.1\cdot 1.1\cdots 1.1}_{\mbox{10 fois}}=2.0. -$$ -Alors qu'en réalité -$$ -1.1^{10}=2.5937... -$$ -Soit une erreur de près de 1/5e! - -. . . - -## Le même phénomène se produit (à plus petite échelle) avec les `float` ou `double`. - -# And now for something completely different - -\Huge La récursivité - -# Exemple de récursivité (1/2) - -## La factorielle - -```C -int factorial(int n) { - if (n > 1) { - return n * factorial(n - 1); - } else { - return 1; - } -} -``` - -. . . - -## Que se passe-t-il quand on fait `factorial(4)`? - -. . . - -## On empile les appels - -+----------------+----------------+----------------+----------------+ -| | | | `factorial(1)` | -+----------------+----------------+----------------+----------------+ -| | | `factorial(2)` | `factorial(2)` | -+----------------+----------------+----------------+----------------+ -| | `factorial(3)` | `factorial(3)` | `factorial(3)` | -+----------------+----------------+----------------+----------------+ -| `factorial(4)` | `factorial(4)` | `factorial(4)` | `factorial(4)` | -+----------------+----------------+----------------+----------------+ - -# Exemple de récursivité (2/2) - -## La factorielle - -```C -int factorial(int n) { - if (n > 1) { - return n * factorial(n - 1); - } else { - return 1; - } -} -``` - -. . . - -## Que se passe-t-il quand on fait `factorial(4)`? - -. . . - -## On dépile les calculs - -+----------------+----------------+----------------+----------------+ -| `1` | | | | -+----------------+----------------+----------------+----------------+ -| `factorial(2)` | `2 * 1 = 2` | | | -+----------------+----------------+----------------+----------------+ -| `factorial(3)` | `factorial(3)` | `3 * 2 = 6` | | -+----------------+----------------+----------------+----------------+ -| `factorial(4)` | `factorial(4)` | `factorial(4)` | `4 * 6 = 24` | -+----------------+----------------+----------------+----------------+ - -# La récursivité (1/4) - -## Formellement - -* Une condition de récursivité - qui *réduit* les cas successifs vers... -* Une condition d'arrêt - qui retourne un résultat - -## Pour la factorielle, qui est qui? - -```C -int factorial(int n) { - if (n > 1) { - return n * factorial(n - 1); - } else { - return 1; - } -} -``` - -# La récursivité (2/4) - -## Formellement - -* Une condition de récursivité - qui *réduit* les cas successifs vers... -* Une condition d'arrêt - qui retourne un résultat - -## Pour la factorielle, qui est qui? - -```C -int factorial(int n) { - if (n > 1) { // Condition de récursivité - return n * factorial(n - 1); - } else { // Condition d'arrêt - return 1; - } -} -``` - -# La récursivité (3/4) - -## Exercice: trouver l'$\varepsilon$-machine pour un `double` - -. . . - -Rappelez-vous vous l'avez fait en style **impératif** plus tôt. - -. . . - -```C -double epsilon_machine(double eps) { - if (1.0 + eps != 1.0) { - return epsilon_machine(eps / 2.0); - } else { - return eps; - } -} -``` - -# La récursivité (4/4) - -\footnotesize - -## Exercice: que fait ce code récursif? - -```C -void recurse(int n) { - printf("%d ", n % 2); - if (n / 2 != 0) { - recurse(n / 2); - } else { - printf("\n"); - } -} -recurse(13); -``` - -. . . - -```C -recurse(13): n = 13, n % 2 = 1, n / 2 = 6, - recurse(6): n = 6, n % 2 = 0, n / 2 = 3, - recurse(3): n = 3, n % 2 = 1, n / 2 = 1, - recurse(1): n = 1, n % 2 = 1, n / 2 = 0. - -// affiche: 1 1 0 1 -``` - -. . . - -Affiche la représentation binaire d'un nombre! diff --git a/slides/cours_6.md b/slides/cours_6.md new file mode 100644 index 0000000..311016a --- /dev/null +++ b/slides/cours_6.md @@ -0,0 +1,946 @@ +--- +title: "Récursivité et complexité" +date: "2023-10-31" +--- + +# Nombres à virgule (1/3) + +## Comment manipuler des nombres à virgule? + +$$ +0.1 + 0.2 = 0.3. +$$ + +Facile non? + +. . . + +## Et ça? + +```C +#include <stdio.h> +#include <stdlib.h> +int main(int argc, char *argv[]) { + float a = atof(argv[1]); + float b = atof(argv[2]); + printf("%.10f\n", (double)(a + b)); +} +``` + +. . . + +## Que se passe-t-il donc? + +# Nombres à virgule (2/3) + +## Nombres à virgule fixe + ++-------+-------+-------+-------+-----+----------+----------+----------+----------+ +| $2^3$ | $2^2$ | $2^1$ | $2^0$ | `.` | $2^{-1}$ | $2^{-2}$ | $2^{-3}$ | $2^{-4}$ | ++-------+-------+-------+-------+-----+----------+----------+----------+----------+ +| `1` | `0` | `1` | `0` | `.` | `0` | `1` | `0` | `1` | ++-------+-------+-------+-------+-----+----------+----------+----------+----------+ + +## Qu'est-ce ça donne en décimal? + +. . . + +$$ +2^3+2^1+\frac{1}{2^2}+\frac{1}{2^4} = 8+2+0.5+0.0625=10.5625. +$$ + +## Limites de cette représentation? + +. . . + + +* Tous les nombres `> 16`. +* Tous les nombres `< 0.0625`. +* Tous les nombres dont la décimale est pas un multiple de `0.0625`. + +# Nombres à virgule (3/3) + +## Nombres à virgule fixe + +* Nombres de $0=0000.0000$ à $15.9375=1111.1111$. +* Beaucoup de "trous" (au moins $0.0625$) entre deux nombres. + +## Solution partielle? + +. . . + +* Rajouter des bits. +* Bouger la virgule. + +# Nombres à virgule flottante (1/2) + +## Notation scientifique + +* Les nombres sont représentés en terme: + * Une mantisse + * Une base + * Un exposant + +$$ +\underbrace{22.1214}_{\mbox{nombre}}=\underbrace{221214}_{\mbox{mantisse}}\cdot +{\underbrace{10}_{\mbox{base}}}{\overbrace{^{-4}}^{\mbox{exp.}}}, +$$ + +. . . + +On peut donc séparer la représentation en 2: + +* La mantisse +* L'exposant + +# Nombres à virgule flottante (2/2) + +## Quel est l'avantage? + +. . . + +On peut représenter des nombres sur énormément d'ordres de grandeur avec un +nombre de bits fixés. + +## Différence fondamentale avec la virgule fixe? + +. . . + +La précision des nombres est **variable**: + +* On a uniquement un nombre de chiffres **significatifs**. +$$ +123456\cdot 10^{23}+ 123456\cdot 10^{-23}. +$$ + +## Quel inconvénient y a-t-il? + +. . . + +Ce mélange d'échelles entraîne un **perte de précision**. + +# Nombres à virgule flottante simple précision (1/4) + +Aussi appelés *IEEE 754 single-precision binary floating point*. + +](figs/Float_example_bare.svg) + +## Spécification + +* 1 bit de signe, +* 8 bits d'exposant, +* 23 bits de mantisse. + +$$ +(-1)^{b_{31}}\cdot 2^{(b_{30}b_{29}\dots b_{23})_{2}-127}\cdot (1.b_{22}b_{21}\dots b_{0})_{2}, +$$ + +## Calculer la valeur décimale du nombre ci-dessus + +# Nombres à virgule flottante simple précision (2/4) + +](figs/Float_example.svg) + +. . . + +\begin{align} +\mbox{exposant}&=\sum_{i=0}^7 b_{23+i}2^i=2^2+2^3+2^4+2^5+2^6=124-127,\\ +\mbox{mantisse}&=1+\sum_{i=1}^{23}b_{23-i}2^{-i}=1+2^{-2}=1.25,\\ +&\Rightarrow (-1)^0\cdot 2^{-3}\cdot 1.25=0.15625 +\end{align} + +# Nombres à virgule flottante simple précision (3/4) + +## Quel nombre ne peux pas être vraiment représenté? + +. . . + +## Zéro: exception pour l'exposant + +* Si l'exposant est `00000000` (zéro) +$$ +(-1)^{\mbox{sign}}\cdot 2^{-126}\cdot 0.\mbox{mantisse}, +$$ +* Sinon si l'exposant est `00000001` à `11111110` +$$ +\mbox{valeur normale}, +$$ +* Sinon `11111111` donne `NaN`. + +# Nombres à virgule flottante simple précision (4/4) + +## Quels sont les plus petits/grands nombres positifs représentables? + +. . . + +\begin{align} +0\ 0\dots0\ 0\dots01&=2^{-126}\cdot 2^{-23}=1.4...\cdot +10^{-45},\\ +0\ 1\dots10\ 1\dots1&=2^{127}\cdot (2-2^{-23})=3.4...\cdot +10^{38}. +\end{align} + +## Combien de chiffres significatifs en décimal? + +. . . + +* 24 bits ($23 + 1$) sont utiles pour la mantisse, soit $2^{24}-1$: + * La mantisse fait $\sim2^{24}\sim 10^7$, + * Ou encore $\sim \log_{10}(2^{24})\sim 7$. +* Environ **sept** chiffres significatifs. + +# Nombres à virgule flottante double précision (64bits) + +## Spécification + +* 1 bit de signe, +* 11 bits d'exposant, +* 52 bits de mantisse. + +. . . + +## Combien de chiffres significatifs? + +* La mantisse fait $\sim 2^{53}\sim10^{16}$, +* Ou encore $\sim \log_{10}(2^{53})\sim 16$, +* Environ **seize** chiffres significatifs. + +## Plus petit/plus grand nombre représentable? + +. . . + +* Plus petite mantisse et exposant: $\sim 2^{-52}\cdot 2^{-1022}\sim 4\cdot 10^{-324}$, +* Plus grande mantisse et exposant: $\sim 2\cdot 2^{1023}\sim \cdot 1.8\cdot 10^{308}$. + +# Précision finie (1/3) + +## Erreur de représentation + +* Les nombres réels ont potentiellement un **nombre infini** de décimales + * $1/3=0.\overline{3}$, + * $\pi=3.1415926535...$. +* Les nombres à virgule flottante peuvent en représenter qu'un **nombre + fini**. + * $1/3\cong 0.33333$, erreur $0.00000\overline{3}$. + * $\pi\cong3.14159$, erreur $0.0000026535...$. + +On rencontre donc des **erreurs de représentation** ou **erreurs +d'arrondi**. + +. . . + +## Et quand on calcule? + +* Avec deux chiffres significatifs +\begin{align} +&8.9+(0.02+0.04)=8.96=9.0,\\ +&(8.9+0.02)+0.04=8.9+0.04=8.9. +\end{align} + +. . . + +## Même pas associatif! + +# Précision finie (2/3) + +## Erreur de représentation virgule flottante + +$$ +(1.2)_{10} = 1.\overline{0011}\cdot 2^0\Rightarrow 0\ 01111111\ +00110011001100110011010. +$$ +Erreur d'arrondi dans les deux derniers bits et tout ceux qui viennent +ensuite +$$ +\varepsilon_2 = (00000000000000000000011)_2. +$$ +Ou en décimal +$$ +\varepsilon_{10} = 4.76837158203125\cdot 10^{-8}. +$$ + +# Précision finie (3/3) + +## Comment définir l'égalité de 2 nombres à virgule flottante? + +. . . + +Ou en d'autres termes, pour quel $\varepsilon>0$ (appelé `epsilon-machine`) on a +$$ +1+\varepsilon = 1, +$$ +pour un nombre à virgule flottante? + +. . . + +Pour un `float` (32 bits) la différence est à +$$ +2^{-23}=1.19\cdot 10^{-7}, +$$ +Soit la précision de la mantisse. + +## Comment le mesurer (par groupe)? + +. . . + +```C +float eps = 1.0; +while ((float)1.0 + (float)0.5 * eps != (float)1.0) { + eps = (float)0.5 * eps; +} +printf("eps = %g\n", eps); +``` + +# Erreurs d'arrondi + +Et jusqu'ici on a encore pas fait d'arithmétique! + +## Multiplication avec deux chiffres significatifs, décimal + +$$ +(1.1)_{10}\cdot (1.1)_{10}=(1.21)_{10}=(1.2)_{10}. +$$ +En continuant ce petit jeu: +$$ +\underbrace{1.1\cdot 1.1\cdots 1.1}_{\mbox{10 fois}}=2.0. +$$ +Alors qu'en réalité +$$ +1.1^{10}=2.5937... +$$ +Soit une erreur de près de 1/5e! + +. . . + +## Le même phénomène se produit (à plus petite échelle) avec les `float` ou `double`. + +# And now for something completely different + +\Huge La récursivité + +# La factorielle: Code impératif + +* Code impératif + +```C +int factorial(int n) { + int f = 1; + for (int i = 1; i < n; ++i) { + f *= i; + } + return f; +} +``` + +# Exemple de récursivité (1/2) + +## La factorielle + +```C +int factorial(int n) { + if (n > 1) { + return n * factorial(n - 1); + } else { + return 1; + } +} +``` + +. . . + +## Que se passe-t-il quand on fait `factorial(4)`? + +. . . + +## On empile les appels + ++----------------+----------------+----------------+----------------+ +| | | | `factorial(1)` | ++----------------+----------------+----------------+----------------+ +| | | `factorial(2)` | `factorial(2)` | ++----------------+----------------+----------------+----------------+ +| | `factorial(3)` | `factorial(3)` | `factorial(3)` | ++----------------+----------------+----------------+----------------+ +| `factorial(4)` | `factorial(4)` | `factorial(4)` | `factorial(4)` | ++----------------+----------------+----------------+----------------+ + +# Exemple de récursivité (2/2) + +## La factorielle + +```C +int factorial(int n) { + if (n > 1) { + return n * factorial(n - 1); + } else { + return 1; + } +} +``` + +. . . + +## Que se passe-t-il quand on fait `factorial(4)`? + +. . . + +## On dépile les calculs + ++----------------+----------------+----------------+----------------+ +| `1` | | | | ++----------------+----------------+----------------+----------------+ +| `factorial(2)` | `2 * 1 = 2` | | | ++----------------+----------------+----------------+----------------+ +| `factorial(3)` | `factorial(3)` | `3 * 2 = 6` | | ++----------------+----------------+----------------+----------------+ +| `factorial(4)` | `factorial(4)` | `factorial(4)` | `4 * 6 = 24` | ++----------------+----------------+----------------+----------------+ + +# La récursivité (1/4) + +## Formellement + +* Une condition de récursivité - qui *réduit* les cas successifs vers... +* Une condition d'arrêt - qui retourne un résultat + +## Pour la factorielle, qui est qui? + +```C +int factorial(int n) { + if (n > 1) { + return n * factorial(n - 1); + } else { + return 1; + } +} +``` + +# La récursivité (2/4) + +## Formellement + +* Une condition de récursivité - qui *réduit* les cas successifs vers... +* Une condition d'arrêt - qui retourne un résultat + +## Pour la factorielle, qui est qui? + +```C +int factorial(int n) { + if (n > 1) { // Condition de récursivité + return n * factorial(n - 1); + } else { // Condition d'arrêt + return 1; + } +} +``` + +# La récursivité (3/4) + +## Exercice: trouver l'$\varepsilon$-machine pour un `double` + +. . . + +Rappelez-vous vous l'avez fait en style **impératif** plus tôt. + +. . . + +```C +double epsilon_machine(double eps) { + if (1.0 + eps != 1.0) { + return epsilon_machine(eps / 2.0); + } else { + return eps; + } +} +``` + +# La récursivité (4/4) + +\footnotesize + +## Exercice: que fait ce code récursif? + +```C +void recurse(int n) { + printf("%d ", n % 2); + if (n / 2 != 0) { + recurse(n / 2); + } else { + printf("\n"); + } +} +recurse(13); +``` + +. . . + +```C +recurse(13): n = 13, n % 2 = 1, n / 2 = 6, + recurse(6): n = 6, n % 2 = 0, n / 2 = 3, + recurse(3): n = 3, n % 2 = 1, n / 2 = 1, + recurse(1): n = 1, n % 2 = 1, n / 2 = 0. + +// affiche: 1 1 0 1 +``` + +. . . + +Affiche la représentation binaire d'un nombre! + +# Exercice: réusinage et récursivité (1/4) + +## Réusiner le code du PGCD avec une fonction récursive + +## Étudier l'exécution + +```C +42 = 27 * 1 + 15 +27 = 15 * 1 + 12 +15 = 12 * 1 + 3 +12 = 3 * 4 + 0 +``` + +# Exercice: réusinage et récursivité (2/4) + +## Réusiner le code du PGCD avec une fonction récursive + +## Étudier l'exécution + +```C +42 = 27 * 1 + 15 | PGCD(42, 27) +27 = 15 * 1 + 12 | PGCD(27, 15) +15 = 12 * 1 + 3 | PGCD(15, 12) +12 = 3 * 4 + 0 | PGCD(12, 3) +``` + +# Exercice: réusinage et récursivité (3/4) + +## Réusiner le code du PGCD avec une fonction récursive + +## Étudier l'exécution + +```C +42 = 27 * 1 + 15 | PGCD(42, 27) +27 = 15 * 1 + 12 | PGCD(27, 15) +15 = 12 * 1 + 3 | PGCD(15, 12) +12 = 3 * 4 + 0 | PGCD(12, 3) +``` + +## Effectuer l'empilage - dépilage + +. . . + +```C +PGCD(12, 3) | 3 +PGCD(15, 12) | 3 +PGCD(27, 15) | 3 +PGCD(42, 27) | 3 +``` + +# Exercice: réusinage et récursivité (4/4) + +## Écrire le code + +. . . + +```C +int pgcd(int n, int m) { + if (n % m > 0) { + return pgcd(m, n % m); + } else { + return m; + } +} +``` + +# La suite de Fibonacci (1/2) + +## Règle + +$$ +\mathrm{Fib}(n) = \mathrm{Fib}(n-1) + \mathrm{Fib}(n-2),\quad +\mathrm{Fib}(0)=0,\quad \mathrm{Fib}(1)=1. +$$ + +## Exercice: écrire la fonction $\mathrm{Fib}$ en récursif et impératif + +. . . + +## En récursif (6 lignes) + +```C +int fib(int n) { + if (n > 1) { + return fib(n - 1) + fib(n - 2); + } + return n; +} +``` + +# La suite de Fibonacci (2/2) + +## Et en impératif (11 lignes) + +```C +int fib_imp(int n) { + int fib0 = 1; + int fib1 = 1; + int fib = n == 0 ? 0 : fib1; + for (int i = 2; i < n; ++i) { + fib = fib0 + fib1; + fib0 = fib1; + fib1 = fib; + } + return fib; +} +``` + +# Exponentiation rapide ou indienne (1/4) + +## But: Calculer $x^n$ + +* Quel est l'algorithmie le plus simple que vous pouvez imaginer? + +. . . + +```C +int pow(int x, int n) { + if (0 == n) { + return 1; + } + for (int i = 1; i < n; ++i) { + x = x * x; // x *= x + } + return x; +} +``` + +* Combien de multiplication et d'assignations en fonction de `n`? + +. . . + +* `n` assignations et `n` multiplications. + +# Exponentiation rapide ou indienne (2/4) + +* Proposez un algorithme naïf et récursif + +. . . + +```C +int pow(x, n) { + if (n != 0) { + return x * pow(x, n-1); + } else { + return 1; + } +} +``` + +# Exponentiation rapide ou indienne (3/4) + +## Exponentiation rapide ou indienne de $x^n$ + +* Écrivons $n=\sum_{i=0}^{d-1}b_i 2^i,\ b_i=\{0,1\}$ (écriture binaire sur $d$ bits, avec +$d\sim\log_2(n)$). +* +$$ +x^n={x^{2^0}}^{b_0}\cdot {x^{2^1}}^{b_1}\cdots {x^{2^{d-1}}}^{b_{d-1}}. +$$ +* On a besoin de $d$ calculs pour les $x^{2^i}$. +* On a besoin de $d$ calculs pour évaluer les produits de tous les termes. + +## Combien de calculs en terme de $n$? + +. . . + +* $n$ est représenté en binaire avec $d$ bits $\Rightarrow d\sim\log_2(n)$. +* il y a $2\log_2(n)\sim \log_2(n)$ calculs. + +# Exponentiation rapide ou indienne (4/4) + +## Le vrai algorithme + +* Si n est pair: calculer $\left(x^{n/2}\right)^2$, +* Si n est impair: calculer $x \cdot \left(x^{(n-1)/2}\right)^2$. + +## Exercice: écrire l'algorithme récursif correspondant + +. . . + +```C +double pow(double x, int n) { + if (1 == n) { + return x; + } else if (n % 2 == 0) { + return pow(x, n / 2) * pow(x, n/2); + } else { + return x * pow(x, (n-1)); + } +} +``` + + +# Efficacité d'un algorithmique + +Comment mesurer l'efficacité d'un algorithme? + +. . . + +* Mesurer le temps CPU, +* Mesurer le temps d'accès à la mémoire, +* Mesurer la place prise mémoire, + +. . . + +Dépendant du **matériel**, du **compilateur**, des **options de compilation**, etc! + +## Mesure du temps CPU + +```C +#include <time.h> +struct timespec tstart={0,0}, tend={0,0}; +clock_gettime(CLOCK_MONOTONIC, &tstart); +// some computation +clock_gettime(CLOCK_MONOTONIC, &tend); +printf("computation about %.5f seconds\n", + ((double)tend.tv_sec + 1e-9*tend.tv_nsec) - + ((double)tstart.tv_sec + 1e-9*tstart.tv_nsec)); +``` + +# Programme simple: mesure du temps CPU + +## Preuve sur un [petit exemple](../source_codes/complexity/sum.c) + +```bash +source_codes/complexity$ make bench +RUN ONCE -O0 +the computation took about 0.00836 seconds +RUN ONCE -O3 +the computation took about 0.00203 seconds +RUN THOUSAND TIMES -O0 +the computation took about 0.00363 seconds +RUN THOUSAND TIMES -O3 +the computation took about 0.00046 seconds +``` + +Et sur votre machine les résultats seront **différents**. + +. . . + +## Conclusion + +* Nécessité d'avoir une mesure indépendante du/de la + matériel/compilateur/façon de mesurer/météo. + +# Analyse de complexité algorithmique (1/4) + +* On analyse le **temps** pris par un algorithme en fonction de la **taille de + l'entrée**. + +## Exemple: recherche d'un élément dans une liste triée de taille N + +```C +int sorted_list[N]; +bool in_list = is_present(N, sorted_list, elem); +``` + +* Plus `N` est grand, plus l'algorithme prend de temps sauf si... + +. . . + +* l'élément est le premier de la liste (ou à une position toujours la même). +* ce genre de cas pathologique ne rentre pas en ligne de compte. + +# Analyse de complexité algorithmique (2/4) + +## Recherche linéaire + +```C +bool is_present(int n, int tab[], int elem) { + for (int i = 0; i < n; ++i) { + if (tab[i] == elem) { + return true; + } else if (elem < tab[i]) { + return false; + } + } + return false; +} +``` + +* Dans le **meilleurs des cas** il faut `1` comparaison. +* Dans le **pire des cas** (élément absent p.ex.) il faut `n` comparaisons. + +. . . + +La **complexité algorithmique** est proportionnelle à `N`: on double la taille +du tableau $\Rightarrow$ on double le temps pris par l'algorithme. + +# Analyse de complexité algorithmique (3/4) + +## Recherche dichotomique + +```C +bool is_present_binary_search(int n, int tab[], int elem) { + int left = 0; + int right = n - 1; + while (left <= right) { + int mid = (right + left) / 2; + if (tab[mid] < elem) { + left = mid + 1; + } else if (tab[mid] > elem) { + right = mid - 1; + } else { + return true; + } + } + return false; +} +``` + +# Analyse de complexité algorithmique (4/4) + +## Recherche dichotomique + +](figs/Binary_search_complexity.svg){width=80%} + +. . . + +* Dans le **meilleurs de cas** il faut `1` comparaison. +* Dans le **pire des cas** il faut $\log_2(N)+1$ comparaisons + +. . . + +## Linéaire vs dichotomique + +* $N$ vs $\log_2(N)$ comparaisons logiques. +* Pour $N=1000000$: `1000000` vs `21` comparaisons. + +# Notation pour la complexité + +## Constante de proportionnalité + +* Pour la recherche linéaire ou dichotomique, on a des algorithmes qui sont $\sim N$ ou $\sim \log_2(N)$ +* Qu'est-ce que cela veut dire? + +. . . + +* Temps de calcul est $t=C\cdot N$ (où $C$ est le temps pris pour une comparaisons sur une machine/compilateur donné) +* La complexité ne dépend pas de $C$. + +## Le $\mathcal{O}$ de Leibnitz + +* Pour noter la complexité d'un algorithme on utilise le symbole $\mathcal{O}$ (ou "grand Ô de"). +* Les complexités les plus couramment rencontrées sont + +. . . + +$$ +\mathcal{O}(1),\quad \mathcal{O}(\log(N)),\quad \mathcal{O}(N),\quad +\mathcal{O}(\log(N)\cdot N), \quad \mathcal{O}(N^2), \quad +\mathcal{O}(N^3). +$$ + +# Ordres de grandeur + +\begin{table}[!h] +\begin{center} +\caption{Valeurs approximatives de quelques fonctions usuelles de complexité.} +\medskip +\begin{tabular}{|c|c|c|c|c|} +\hline +$\log_2(N)$ & $\sqrt{N}$ & $N$ & $N\log_2(N)$ & $N^2$ \\ +\hline\hline +$3$ & $3$ & $10$ & $30$ & $10^2$ \\ +\hline +$6$ & $10$ & $10^2$ & $6\cdot 10^2$ & $10^4$ \\ +\hline +$9$ & $31$ & $10^3$ & $9\cdot 10^3$ & $10^6$ \\ +\hline +$13$ & $10^2$ & $10^4$ & $1.3\cdot 10^5$ & $10^8$ \\ +\hline +$16$ & $3.1\cdot 10^2$ & $10^5$ & $1.6\cdot 10^6$ & $10^{10}$ \\ +\hline +$19$ & $10^3$ & $10^6$ & $1.9\cdot 10^7$ & $10^{12}$ \\ +\hline +\end{tabular} +\end{center} +\end{table} + + +# Quelques exercices (1/3) + +## Complexité de l'algorithme de test de primalité naïf? + +```C +for (i = 2; i < sqrt(N); ++i) { + if (N % i == 0) { + return false; + } +} +return true; +``` + +. . . + +## Réponse + +$$ +\mathcal{O}(\sqrt{N}). +$$ + +# Quelques exercices (2/3) + +## Complexité de trouver le minimum d'un tableau? + +```C +int min = MAX; +for (i = 0; i < N; ++i) { + if (tab[i] < min) { + min = tab[i]; + } +} +return min; +``` + +. . . + +## Réponse + +$$ +\mathcal{O}(N). +$$ + +# Quelques exercices (3/3) + +## Complexité du tri par sélection? + +```C +int ind = 0 +while (ind < SIZE-1) { + min = find_min(tab[ind:SIZE]); + swap(min, tab[ind]); + ind += 1 +} +``` + +. . . + +## Réponse + +### `min = find_min` + +$$ +(N-1)+(N-2)+...+2+1=\sum_{i=1}^{N-1}i=N\cdot(N-1)/2=\mathcal{O}(N^2). +$$ + +## Finalement + +$$ +\mathcal{O}(N^2\mbox{ comparaisons}) + \mathcal{O}(N\mbox{swaps})=\mathcal{O}(N^2). +$$ + + -- GitLab