• Aucun résultat trouvé

les graphes orientés sans circuit

En plus de l’intervalle encadrant la valeur exacte de chaque nombre manipulé, il est nécessaire de conserver une trace de son histoire, de manière à pouvoir, le cas échéant, être capable de calculer sa valeur exacte. Cet historique est tout naturellement conservé sous sa forme symbolique la plus simple : un arbre d’expressions. Tous les nombres manipulés sont soit une donnée initiale du programme, soit le résultat d’une opération.

Toute valeur manipulée par l’arithmétique paresseuse est vue comme étant la racine d’un arbre d’expressions arithmétiques dont chaque nœud est une opération (unaire ou binaire) et chaque feuille un nombre rationnel (donnée initiale du programme ou calculé). a b, [ ] a ( )–1 sa 2Ebias+eama = b ( )–1 sb 2Ebias+ebmb = a b, [ ] W a b([ , ]) W 0 a([ , ]) a W 0 a[ , ] = 2p(Ebias+ea) +ma+1 W a b([ , ]) = W 0 b([ , ])–W 0 a([ , ]) W a b([ , ]) = 2p(( )–1 sb(Ebias+eb) – ( )–1 sa(Ebias+ea)) + (mbma) W 2([ –200,2–100]) = 2p((Ebias–100) – (Ebias–200)) = 2p×100 W 2([ 100,2101]) = 2p((Ebias+101) – (Ebias+100)) = 2p

La paresse Arithmétique sur les graphes orientés sans circuit

Tous les nombres sont d’un des types suivants : • un nombre rationnel

• une opération binaire (addition ou multiplication) avec comme opérandes deux sous arbres, et comme valeur un intervalle encadrant le résultat de cette opération. • une opération unaire (inverse ou opposé) avec comme opérande un sous arbre et

comme valeur un intervalle encadrant le résultat de cette opération.

Toutes les entités que l’arithmétique manipule sont des nombres d’un des trois types vus précédemment et il est tout à fait possible de construire des expressions du type sans être obligé de dupliquer le nombre , ou le nombre dans le calcul de factorielle si on l’écrit de manière récursive ( ).

Le fait de pouvoir partager des expressions transforme l’arbre d’expressions initialement utilisé en un graphe sans circuit (il est impossible d’utiliser un résultat qui n’a pas encore été calculé). Une telle structure de données porte le nom de graphes orientés sans circuit (“direct acyclic graph” ou DAG en anglais). Son intérêt principal, outre le fait que son utilisation diminue le nombre de nœuds présents dans les expressions, est de faire bénéficier de l’évaluation d’un sous-arbre partagé tous les arbres qui l’uti-lisent. ax+b a x b ax figure 9

arbre d’expression élémentaire

x + c b a ab c b ---+ c b ---ab figure 10

Partage de valeurs dans une expression

x / + n 1 -x -x -x x fact(n) ab+c bb n n fact n( ) = n×fact n( –1)

Arithmétique sur les graphes orientés sans circuit La paresse

Les diverses représentations de notre arithmétique en place, il s’agit maintenant de l’utiliser. Chaque nombre, comme nous venons de le voir, est soit une représentation exacte, soit une expression symbolique. Les opérations entre ces deux types de valeurs sont banalisées et se décomposent en trois cas dont le résultat est toujours une ex-pression symbolique :

• Opération entre deux représentations exactes. • Opération entre deux expressions symboliques.

• Opération entre une expression symbolique et une représentation exacte.

A aucun moment, pour l’instant il n’a été nécessaire de faire des calculs exacts, on se contente simplement de maintenir à jour de manière adéquate les encadrements des résultats exacts tant que cela s’avère possible. Notre arithmétique paresseuse se comporte, vue de l’extérieur, comme une arithmétique d’intervalles.

Le coût des opérations (+, -, *) est constant. Lors de chaque opération il est seulement nécessaire de créer le nœud du DAG représentant cette opération et de mettre à jour de manière correcte l’intervalle encadrant le résultat. La division, quant à elle, se fait aussi en temps constant si l’encadrement du diviseur ne contient pas zéro, mais pro-voque en revanche l’évaluation de tout ou partie de l’arbre d’expression dans le cas contraire. Le coût de l’opération dépendra de la complexité de l’expression représentant le dénominateur ; nous étudierons ce cas particulier dans le § 4.4.1 . Les calculs d’en-cadrement du résultat de l’opération sont réalisés par l’arithmétique d’intervalles en utilisant uniquement les encadrements des opérandes de l’opération.

Il faut remarquer que toutes les opérations s’accompagnent de l’occupation de places mémoire constantes. A chaque opération on doit créer un nouveau nœud dans le DAG (l’expression utilise 1 nœud *, un nœud + et 3 feuilles si , et sont des nombres rationnels). A la complexité en nombre d’opérations d’un algorithme vient s’ajouter une complexité en nombre de nœuds utilisés dans l’arbre qui est du même ordre. Ainsi un algorithme qui serait en opérations sera aussi en pour ce qui est de l’espace mémoire utilisé par les nœuds. Ce cas extrême n’est atteint que lorsque aucun calcul exact n’a été effectué, chaque évaluation faisant disparaître un certain nombre de nœuds dans le DAG. Dans l’autre cas extrême, celui où tout est évalué, la complexité en nombre de nœuds se rapproche de la complexité habituelle en place mémoire de l’algorithme. Il faut se garder de conclure hativement : la quantité de mémoire utilisée par les nœuds de l’arbre peut être importante, mais, lors d’une évaluation, ces nœuds seront remplacés par des nombres rationnels qui, eux aussi, peuvent occuper beaucoup de place mémoire.

4.4.1 Cas particulier de l’inverse d’un nombre paresseux

Les DAG étant une manière commode de conserver les informations nécessaires, ils doivent tenir compte des contraintes des arithmétiques et surtout ne jamais entrer en

ax+b a x b

La paresse Arithmétique sur les graphes orientés sans circuit

conflit avec elles si l’on veut pouvoir utiliser les expressions symboliques plus tard. Dans le cas des rationnels une division par zéro est illicite. Dans le cas des intervalles, une division par un intervalle dont les bornes ont des signes opposés donne comme résultat la réunion de et ; l’intervalle connexe contenant cette réunion est : cet encadrement ne nous serait pas d’un grand secours pour les calculs à venir. L’apparition d’un encadrement de nombre paresseux contenant zéro peut avoir deux causes :

• Les opérations qui ont fourni ce nombre ont fait grossir l’intervalle encadrant la solu-tion exacte et maintenant il contient zéro.

• L’intervalle en question est bien celui du nombre zéro.

Dans tous les cas une évaluation s’impose. Il faut vérifier que la valeur mise en cause n’est pas zéro (dans le cas contraire on aurait affaire à une véritable division par zéro). Pour effectuer cette vérification on doit évaluer l’expression symbolique du nombre paresseux concerné. Cette opération consiste à calculer récursivement la valeur exacte de ce nombre et à remplacer la description symbolique par la valeur rationnelle. Une comparaison exacte entre ce nouveau rationnel et la valeur zéro permet de savoir si l’opération est licite ou non. Dans le cas où l’on tente une division par zéro, on se trouve face à une erreur et une exception de type “divide by zero” est générée (en général le programme s’arrête brutalement).

Pour la première fois, nous venons de rencontrer un cas où il est nécessaire de faire des calculs exacts. Il s’agit de la façon la plus simple de résoudre le problème posé, nous verrons dans les paragraphes suivants que d’autres méthodes peuvent être en-visagées. L’arithmétique paresseuse prend d’elle même la décision d’évaluer un arbre d’expressions, dans ce contexte précis, pour être sûre de faire une opération valide. Cette décision est prise alors que les intervalles ne sont plus à même de fournir de manière fiable une réponse à la question : le diviseur est-il différent de zéro ?

4.4.2 Comparaison de deux nombres

Une arithmétique sans les opérateurs de comparaison est utilisable, bien sûr, mais limiterait fortement son domaine d’application. Pour pouvoir effectuer tous les tests entre deux nombres, il suffit d’avoir à sa disposition les deux opérateurs : supérieur et égal. Tous les autres opérateurs se construisent sans difficulté à partir de ces deux relations:

si alors rendre faux sinon rendre vrai si alors rendre faux sinon rendre vrai

si ou alors rendre vrai sinon rendre faux si alors rendre faux sinon rendre vrai

Dans tous les cas, la première étape de la comparaison se limite simplement à la comparaison des intervalles des deux opérandes. Si l’intersection entre les intervalles

a b, [ ] ∞ – ,1 b⁄ [ ] [1 a⁄ ,+∞] ∞ – ,+∞ [ ] ab a = b ab a>b ab a>b a = b a<b ab

Stratégies d’évaluation La paresse

et encadrant respectivement les valeurs et est vide, on est certain que ces deux valeurs sont différentes. Pour déterminer leur ordre, il faut com-parer leurs bornes de la manière décrite au § 4.3 soit :

.

Dans le cas où l’intersection entre les deux intervalles n’est pas vide, l’arithmétique d’intervalles est incapable de nous apprendre quoi que ce soit sur les deux nombres paresseux. Il faut avoir recours à une évaluation des expressions symboliques. Dans le cas où les opérandes sont des descriptions symboliques, on calcule les valeurs rationnelles qu’elles représentent puis on les compare de manière à fournir une réponse exacte.

Encore une fois, c’est le contexte qui dicte le comportement de l’arithmétique. Aucune évaluation ne sera faite si ce n’est pas nécessaire : c’est être paresseux.

On peut remarquer que l’utilisation des intervalles ne permet pas de garantir l’égalité de deux nombres paresseux et pour distinguer deux valeurs leurs intervalles ne doivent pas se recouvrir.