Dépassement d'entier (integer overflow)¶
On considère le type suivant :
type entier = Infini | MoinsInfini | Entier of int
type entier = Infini | MoinsInfini | Entier of int
On rappelle que max_int est le plus grand entier représentable en OCaml et min_int le plus petit :
max_int;;
min_int;;
max_int + 1;; (* un dépassement du plus grand entier donne le plus petit *)
- : int = 4611686018427387903
- : int = -4611686018427387904
- : int = -4611686018427387904
Exercice
On voudrait pouvoir additionner deux entier $a$ et $b$ en prennant en compte les dépassements d'entier, et en donnant Infini dans ce cas.
- Pourquoi ne peut-on pas tout simplement tester si $a + b >$
max_int? - Pourquoi peut-on tester à la place si
max_int$ - ~a \leq b$ avec $a \geq 0$ ? - Quel test similaire pourrait-on utiliser pour savoir si $a + b <$
min_int? - Écrire une fonction
add_int : int -> int -> entiertelle queadd_intajoute 2inten tenant compte des dépassements (on renvoieInfinis'il y a un dépassement demax_intetMoinsInfinis'il y a un dépassement demin_intpar le bas). - Écrire une fonction
add : entier -> entier -> entierajoutant 2 entiers en tenant compte des dépassements.
On pourra écrirefailwith "Indeterminé"dans le cas d'une forme indéterminé. - Écrire une fonction
oppose : entier -> entierrenvoyant l'opposé d'un entier. Par exemple,oppose InfinirenvoieMoinsInfini,oppose (Entier a)renvoieEntier (-a)... - Écrire une fonction
subeffectuant la soustraction.
Solution
- Par définition, aucun entier ne peut être supérieur à
max_int. - Déjà, $a + b >$
max_intimplique que $a \geq 0$ (car $b$ est un entier dans est inférieur àmax_int). De plusmax_int$- a$ n'effectue pas de dépassement. Donc le test fonctionne sans risque de dépassement. - $a + b < $
min_intse réécrit en $a < 0$ etmin_int$ - a > b$.
Solution
(* 4. *)
let add_int a b =
if a >= 0 && max_int - a <= b then Infini
else if a <= 0 && min_int - a >= b then MoinsInfini
else Entier (a + b);;
add_int 4611686018427387900 2;;
add_int 4611686018427387900 5;;
add_int 4611686018427387900 (-5);;
add_int (-4611686018427387904) 5;;
add_int (-4611686018427387904) (-1);;
(* 5. *)
let add n p = match n, p with
| Infini, MoinsInfini | MoinsInfini, Infini -> failwith "Indeterminé"
| Infini, _ | _, Infini -> Infini
| MoinsInfini, _ | _, MoinsInfini -> MoinsInfini
| Entier a, Entier b -> add_int a b;;
add Infini (Entier 10);;
add (Entier 10) (Entier 4611686018427387895);;
(* 6 *)
let oppose n = match n with
| Infini -> MoinsInfini
| MoinsInfini -> Infini
| Entier a -> Entier (-a);;
(* 7 *)
let sub n p = add n (oppose p);;
sub Infini (Entier 10);;
sub (Entier (-4611686018427387899)) (Entier 10);;
val add_int : int -> int -> entier = <fun>
- : entier = Entier 4611686018427387902
- : entier = Infini
- : entier = Entier 4611686018427387895
- : entier = Entier (-4611686018427387899)
- : entier = MoinsInfini
val add : entier -> entier -> entier = <fun>
- : entier = Infini
- : entier = Infini
val oppose : entier -> entier = <fun>
val sub : entier -> entier -> entier = <fun>
- : entier = Infini
- : entier = MoinsInfini
Pour savoir si $a \times b$ fait un dépassement, on propose la façon suivante :
- Calculer $c = a \times b$
- Regarder si $c/a$ est égal à $b$
- Pourquoi cela fonctionne ? À quelle autre erreur faut-il faire attention dans cette méthode ?
- En déduire une fonction
mult : entier -> entier -> entiermultipliant 2 entiers en tenant compte des dépassements.
Tableaux dynamiques¶
Il est impossible d'ajouter ou supprimer un élément dans tableau (array) en OCaml.
Les tableaux dynamiques (ou : redimensionnables) permettent d'ajouter un élément e, en recréant un tableau plus grand dans lequel on recopie tous les éléments ainsi que e.
Voici la définition de tableau dynamique que nous allons utiliser :
type 'a array_dyn = {mutable t : 'a array; mutable n : int};;
type 'a array_dyn = { mutable t : 'a array; mutable n : int; }
n indique le nombre de cases du tableau t que l'on considère comme faisant partie du tableau dynamique. Les indices au delà de n dans t sont ignorés.
À chaque fois que l'on voudra ajouter un élément e à un tableau dynamique d :
- s'il reste de la place dans
d.t(c'est à dire sid.n < Array.length t), on metedansedansd.t.(n)et on met à jourd.n - sinon, on créé un nouveau tableau
t'de taille2n, on recopied.tdedans, on ajouteepuis on remplaced.tpart'.
Exercice
- Écrire une fonction
addajoutant un élément dans unarray_dyn. - Quelle est la complexité dans le pire cas de
add? - On suppose que l'on ajoute $n$ éléments (avec
addà un tableau dynamique de taille initiale 1. Montrer que la complexité totale de ces $n$ opérations est O($n$) (autrement dit, la complexité moyenne ou complexité amortie d'une opération est O(1))
Solution
(* 1 *)
let copy t1 t2 = (* copie t1 dans t2 *)
for i = 0 to Array.length t1 - 1 do
t2.(i) <- t1.(i)
done;;
let add e d =
if d.n < Array.length d.t then (d.t.(d.n) <- e; d.n <- d.n + 1)
else if d.n = 0 then (d.t <- [|e|]; d.n <- 1)
else (let t' = Array.make (2*d.n) d.t.(0) in
copy d.t t';
t'.(d.n) <- e;
d.t <- t';
d.n <- d.n + 1);;
let d = {t = [||]; n = 0};;
for i = 0 to 10 do
add i d
done;
d
val copy : 'a array -> 'a array -> unit = <fun>
val add : 'a -> 'a array_dyn -> unit = <fun>
val d : '_weak10 array_dyn = {t = [||]; n = 0}
- : int array_dyn =
{t = [|0; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 0; 0; 0; 0; 0|]; n = 11}
Solution
- Dans le pire cas,
add e ddemande de recopier lesnvaleurs ded.t, d'où une complexité O(n). - Il y a des recopies quand
d.nsera égal à 1, 2, 4, 8, ... c'est à dire pour les puissances de 2. $$\sum_{k=0}^{\lfloor \log_2(n) \rfloor} 2^k = ... = O(2^{\lfloor \log_2(n) \rfloor}) = O(n)$$ (on a utilisé la formule de la somme des termes d'une suite géométrique)