Liste des symboles rencontrés dans ce chapitre:
float rationalize numerator denominator realpart imagpart complex + - * / exp expt log isqrt random *random-state* make-random-state troncature arrondi floor ceiling truncate round mod rem = /= < <= > >= min max zerop minusp plusp signum abs evenp oddp gcd lcm sin cos tan asin acos atan sinh cosh tanh asinh acosh atanh *read-base* *print-base*
Les nombres sous lisp.
Avec CLISP les nombres sont auto-évalués.
LES ENTIERS: c'est la taille de la mémoire qui limite la représentation d'un entier.
Deux variables:
most-positive-fixnum
4611686018427387903
most-negative-fixnum
-4611686018427387904
D'autres variables se construisent avec les mots suivants: most ou least, positive ou negative, short-float, single-float, double-float ou long-float.
LES RATIONNELS:
12 >> 12
-12 >> -12
12. >> 12
2/3 >> 2/3
-2/3 >> -2/3
2/-3 >> erreur! il fallait écrire une opération: (/ 2 -3) >> -2/3
4/6 >> 2/3
6/3 >> 2
FLOAT:
(float 1/3)
0.33333334 le nombre rationnel est transformé en flottant
(float 1/3 1d0)
0.3333333333333333d0 ici, en double précision
RATIONALIZE:
(rationalize 2.5)
5/2 2.5 est transformé en rationnel
#b1010 >> 10 la valeur binaire est évaluée dans le système décimal (2³+2¹=10)
#o77 >> 63 la valeur octale est évaluée en base 10: 7*8¹+7*8⁰=63
#xEF >> 239 la valeur hexadécimale est évaluée en base 10: 14*16¹+15*16⁰=239
#20rJ >> 19 J, en base 20, vaut 19, en base 10.
#20rK >> ERREUR le chiffre K n'existe pas en base 20.
NUMERATOR:
DENOMINATOR:
(numerator 2/3)
2 rend le numérateur du nombre rationnel
(denominator 2/3)
3 rend le dénominateur du nombre rationnel
(denominator 7)
1 rend 1 si l'argument est un nombre entier
1.0 >> 1.0
12.0 >> 12.0
12e0 >> 12.0 e est le marqueur d'exposant par défaut (initialement: simple)
12e2 >> 1200.0 12*10²
12e-2 >> 0.12 12*10⁻²
0.12 >> 0.12
.12 >> 0.12
0.123E20 >> 1.23e19
123d15 >> 1.23d17 double précision (d)
1.23s2 >> 123.0 court
123s15 >> 1.23e17 court devient simple
LES COMPLEXES:
#c(2 1) >> #C(2 1)
REALPART:
(realpart #c(2 1))
2 rend la partie réelle du nombre (complexe ou non)
IMAGPART:
(imagpart #c(2 1))
1 rend la partie imaginaire du nombre complexe (0 si non complexe)
#c(2/3 3/4) >> #C(2/3 3/4)
#c(2 1.0) >> #C(2.0 1.0)
#c(2.0 1.0d0)>> #C(2.0d0 1.0d0)
#c(1/2 1.0) >> #C(0.5 1.0)
#c(3 0) >> 3 quand la partie imaginaire est nulle, il s'affiche le nombre réel
#c(1/2 0) >> 1/2
#c(3 0.0) >> #C(3.0 0.0) sauf dans ce cas
#c(-6/3 0) >> -2
Mais:
#c((sqrt 2) 1)
Nous obtenons un message d'erreur disant que (sqrt 2) n'est pas de type réel.
En effet (sqrt 2) est un calcul (non effectué) et non pas un nombre.
La solution est de construire le nombre complexe à l'aide de la fonction suivante:
COMPLEX:
(complex (sqrt 2) 0)
#C(1.4142135 0.0)
Cette fois (sqrt 2) a été calculé et le nombre complexe rendu.
Cette fonction admet deux arguments: la partie réelle et la partie imaginaire.
(realpart (complex (sqrt 2) 0))
1.4142135
LES OPERATIONS: +, -, *, /, exp, expt
EXP: rend e élevé à la puissance de l'argument (e est la base des logarithmes naturels).
(exp 1)
2.7182817
24.532532 e puissance 3,2
EXPT: rend le premier argument élevé à la puissance du deuxième argument.
(expt 2.5 2)
6.25 2,5 puissance 2 (ici, le résultat est du type float)
(expt 2 0)
1
(expt 0 0)
1
(expt 0 0.0) >> erreur si le 2ème argument n'est pas un entier
(sqrt (expt -1 3)) <> (expt -1 3/2) deux résultats différents: i et -i !!!!!
Mais les deux résultats sont bons. Voir les fonctions racines ou racines-équation pour a=1, b=0, c=1.
(racine-équation 1 0 1)
Il y a deux racines complexes:
#C(0.0 1.0)
#C(0.0 -1.0)
(+ 10.0 3.0) >> 13.0
(- 10 3 5) >> 2
(* 2 3 5) >> 30
(/ 10 5 2) >> 1
(expt 2 3) >> 8
(* #c(0 1) #c(1 1)) >> #C(-1 1)
sauf:
(+ #c(1 2) #c(3 -2)) >> 4
(/ 2 3) >> 2/3
(/ 4) >> 1/4
pour les cas où les arguments sont différents:
(+ 1 2.0) >> 3.0
(/ 2 3.0) >> 0.6666667
(+ #c(1 2) 3) >> #c(4 2)
(+ #c(1 2) 3/2) >> #c(5/2 2)
(expt 2.3 3) >> 12.166999 2,3 et le résultat sont du type float
(expt 5/2 2) >> 25/4 5/2 et le résultat sont du type rational
(expt #c(0 1) 2) >> -1 résultat du type rationnel ou entier
(typep (expt #c(1.2 1) 2) 'complex) >> T le résultat est bien du type complex
Remarques: +,-,/,* sont, en fait, des noms de fonctions.
Avec un seul argument - rend l'opposé de l'argument et / rend l'inverse de l'argument:
(/ 3) >> 1/3
Avec zéro argument + rend 0 et * rend 1 (c'est à dire les éléments neutres de ces opérations)- et / n'admettent pas zéro argument et rendent une erreur.
LOG:
(log 100 10) >> 2.0 logarithme de 100 en base 10
(log 100) >> 4.6051702 logarithme népérien de 100
(log (exp 1))
0.99999994
ISQRT: l'argument doit être un entier positif. Rend le plus grand entier inférieur ou égal à la racine carrée
(isqrt 9) >> 3
(isqrt 3) >> 1
RANDOM: (permet des tirages pseudo-aléatoire)
*RANDOM-STATE*: variable dont la valeur est de type random state (état aléatoire)
la valeur initiale dépend de l'implémentation
c'est l'état aléatoire courant utilisé par la fonction RANDOM si l'argument optionnel
d'état aléatoire n'est pas précisé
MAKE-RANDOM-STATE: permet de créer un nouvel état aléatoire qui peut être fourni comme valeur
à *RANDOM-STATE*
Remarque: chaque fois qu'on redémarre SLIME on se trouve dans le même état aléatoire.
En conséquence, toute application utilisant RANDOM au démarrage risque de sortir les mêmes nombres aléatoires.
Pour éviter cet inconvénient, on peut changer l'état aléatoire au démarrage de la façon suivante:
(setq *random-state* (make-random-state))
(random 6)
5 rend un nombre entier entre 0 compris et 6 non compris
(random 2.0) >> ? rend un nombre flottant entre 0 compris et 2 non compris
On peut se demander si les résultats sont bien équiprobables.
Voici une fonction qui permet de faire un nombre nb de tirages sur des nombres entiers inférieurs à b.
Comme le nombre nb doit être assez grand, on pourra utiliser la fonction factorielle pour calculer nb.
Rappelons, d'abord, les formules de la variance et de l'écart-type:
moy= somme (ni*xi)/N V= somme (ni*(xi-moy)²)/N
où N est l'effectif total, les xi représentent la série statistique et les ni les effectifs respectifs de cette série. moy est la moyenne de la série. * représente la multiplication.
(defun tirage-stat (nb b)
"Rend un aperçu de l'équiprobabilité de b nombres entiers positifs pour un nombre de tirages nb."
(let ((*h* (make-hash-table :test #'equal)) (*som* 0) (*moy* 0) (*varxnb* 0) (*sigma* 0))
(clrhash *h*)
(do ((i 0 (1+ i)))
((>= i nb) 'fin)
(let ((x (random b)))
(if (not (gethash x *h*))
(setf (gethash x *h*) 1)
(setf (gethash x *h*) (1+ (gethash x *h*))))))
(format t "Série => effectif: ~%")
(maphash #'(lambda (clé val) (format t "~a => ~a~%" clé val)) *h*)
(maphash #'(lambda (clé val) (setq *som* (+ *som* (* clé val)))) *h*)
(setq *moy* (/ *som* nb)) ;somme de la distribution divisé par l'effectif total
(format t "La moyenne de cette série est: ~,2f.~%" *moy*)
(maphash #'(lambda (clé val) (setq *varxnb* (+ *varxnb* (* val (expt (- clé *moy*) 2))))) *h*)
(setq *sigma* (sqrt (/ *varxnb* nb)))
(format t "Et l'écart-type est: ~,2f.~%" *sigma*)))
Pour la factorielle on pourra prendre la fonction suivante:
(defun fact (n)
(assert (and (integerp n) (> n -1)))
(if (zerop n)
1
(* n (fact (1- n)))))
Exemple d'appel:
(tirage-stat (fact 10) 6)
Série => effectif:
2 => 605011
5 => 605498
4 => 604993
3 => 605034
1 => 604360
0 => 603904
La moyenne de cette série est: 2.50.
Et l'écart-type est: 1.71.
NIL
(fact 10) c'est 3628800 tirages!
Les effectifs de la série (0 1 2 3 4 5) montrent que les tirages semblent équiprobables.
l'écart-type est assez important (par rapport à la moyenne), ce qui est une bonne chose.
Je vous laisse réfléchir sur ce résultat et l'interpréter.
Autre point de vue: étudier la série des effectifs obtenus.
Pour cela, il faut modifier un peu le programme tirage-stat:
Dans les calculs la clé est remplacée par la valeur val.
(defun tirage-stat2 (nb b)
"Rend un aperçu de l'équiprobabilité de b nombres entiers positifs pour un nombre de tirages nb."
(let ((*h* (make-hash-table :test #'equal)) (*som* 0) (*moy* 0) (*varxnb* 0) (*sigma* 0))
(clrhash *h*)
(do ((i 0 (1+ i)))
((>= i nb) 'fin)
(let ((x (random b)))
(if (not (gethash x *h*))
(setf (gethash x *h*) 1)
(setf (gethash x *h*) (1+ (gethash x *h*))))))
(format t "Série => effectif: ~%")
(maphash #'(lambda (clé val) (format t "~a => ~a~%" clé val)) *h*)
(maphash #'(lambda (clé val) (setq *som* (+ *som* (* val val))) clé) *h*)
(setq *moy* (/ *som* nb)) ;somme de la distribution divisé par l'effectif total
(format t "La moyenne de cette série est: ~,2f.~%" *moy*)
(maphash #'(lambda (clé val) (setq *varxnb* (+ *varxnb* (* val (expt (- val *moy*) 2)))) clé) *h*)
(setq *sigma* (sqrt (/ *varxnb* nb)))
(format t "Et l'écart-type est: ~,2f.~%" *sigma*)))
(tirage-stat2 (fact 10) 6)
Série => effectif:
0 => 604043
2 => 604948
1 => 604396
3 => 605157
5 => 604833
4 => 605423
La moyenne de cette série est: 604800.40.
Et l'écart-type est: 460.88.
NIL
Cette fois l'écart-type est faible par rapport à la moyenne.
Les effectifs sont donc bien concentrés autour de la valeur moyenne.
La fonction random donne donc des résultats assez équiprobables.
TRONCATURE ARRONDI:
FLOOR:
CEILING:
TRUNCATE:
ROUND:
(floor -2.7) >> -3 valeur entière juste inférieure
0.29999995 distance à la valeur initiale
(floor 2.7) >> 2
0.70000005
(ceiling -2.7) >> -2 valeur entière juste supérieure
-0.70000005 opposé à la distance à la valeur initiale
(ceiling 2.7) >> 3
-0.29999995
(truncate -2.7) >> -2 comme ceiling pour les nombres négatifs
-0.70000005
(truncate 2.7) >> 2 comme floor pour les nombres positifs
0.70000005
(round -2.7) >> -3 arrondi à l'entier le plus proche
0.29999995
(round 2.7) >> 3
-0.29999995
Ces 4 fonctions admettent un 2ème argument optionnel: le diviseur.
(floor 5 2)
2 quotient entier de la division
1 reste de la division, le 2ème résultat est donc différent de:
(floor (/ 5 2))
2
1/2 sous cette forme, on obtient la fraction restante
ffloor, fceiling, ftruncate et fround font la même chose, mais rendent un nombre flottant pour le 1er résultat.
DEUX FONCTIONS APPARENTEES: (n'admettent pas plus que deux arguments)
MOD: (+ (* (floor (/ x y)) y) (mod x y)) === x
REM: (+ (* (truncate (/ x y)) y) (rem x y)) === x
(mod 10 3) >> 1 reste de la division entière de 11 par 3. Les restes de
la division par 3 ne peuvent être que 0, 1, ou 2
(rem 10 3) >> 1 même chose que MOD pour des quotients positifs
(mod -10 3) >> 2 des résultats différents pour les quotients négatifs
(rem -10 3) >> -1
COMPARAISONS NUMERIQUES:
(= 10 20/2) >> T compare les valeurs quelque soit le type
(eql 10 20/2) >> T car 20/2=10
(= 2 2.0) >> T
mais:
(eql 2 2.0) >> NIL 2 et 2.0 n'ont pas le même type
(/= 1 2 3) >> T pris deux à deux les nombres de la liste ne sont pas égaux
(/= 1 2 3 1) >> NIL deux nombres sont égaux dans la liste
(< 2 3 4) >> T
(< 2 3 3) >> NIL
(<= 2 3 3) >> T
Remarque: en CLISP, =,/=,<,<=,>,>= sont, en fait, des fonctions. On peut, donc, les passer en argument à d'autres fonctions.
MIN:
MAX:
(min 3 7 2 9) >> 2
(min 3 -7 2 9) >> -7
(max 3 -7 2 9) >> 9
ZEROP;
MINUSP:
PLUSP:
pour ces trois prédicats l'argument doit être un nombre
(zerop 5) >> NIL car 5 n'est pas nul
(zerop (- 4 (* 2 2))) >> T l'évaluation de (- 4 (* 2 2)) donne bien zéro
(minusp -7) >> T car -7 est négatif
(minusp -0.0)
NIL
car -0.0 = 0
(plusp -7) >> NIL car -7 n'est pas positif
SIGNUM:
(mapcar #'signum '(-3 5 0 -0.0 0.0))
(-1 1 0 -0.0 0.0) rend -1 si le nombre est négatif, 1 s'il est positif, 0 s'il est nul (avec une petite variation pour -0.0 et 0.0
En fait, le résultat donne une indication du type:
(signum -0.5)
-1.0 single-float
(signum -0.5d0)
-1.0d0 double-float
ABS: (* (abs x) (signum x)) = x
(abs -5.0)
5.0 rend la valeur absolue du nombre
EVENP: (prédicat) l'argument doit être un entier.
rend T si l'argument est pair
(evenp 4) >> T car 4 est pair
ODDP: (prédicat) l'argument doit être un entier.
rend T si l'argument est impair, NIL sinon.
(oddp 5) >> T car 5 est impair
GCD: (gcd &rest integers)
(gcd 60 42)
6
rend le plus grand diviseur commun
LCM: (lcm &rest integers)
(lcm 1 2 3 4 5 6)
60
rend le plus petit multiple commun
FONCTIONS SCIENTIFIQUES:
(exp 1) >> 2.7182817 rend e¹
(log (exp 1)) >> 0.99999994 soit: ln(e)=1
(expt 2 3) >> 8 2³
TRIGONOMETRIE: SIN, COS, TAN, ASIN, ACOS, ATAN.
L'argument de SIN, COS, TAN doit être en radian ou être un nombre complexe.
ASIN et ACOS rendent un nombre complexe si l'argument est inférieur à -1, supérieur à 1 ou est complexe.
ATAN rend un complexe si l'argument est complexe.
(let ((x (/ pi 3)))
(list (sin x) (cos x) (tan x)))
(0.8660254037844386d0 0.5000000000000001d0 1.7320508075688767d0)
(* 180 (/ (acos 0.5) pi))
60.000001669652114d0 soit 60 degrés
Voici une petite fonction qui vous permet d'effectuer une liste de calculs:
(defun effectuerl1 (l)
"effectue chaque action dans la liste, affiche l'action et son résultat."
(if (listp l)
(loop for x in l
do (format t "~a = ~a.~%" x (eval x)))
(error "L'argument doit être une liste.")))
EFFECTUERL1
(effectuerl1 '((+ 2 3) (- 2 3) (> 2 3) (* 2 3) (/ 2 3) (expt 2 3) (car '(2 3)) (cdr '(2 3)) (cons 2 3)))
(+ 2 3) = 5.
(- 2 3) = -1.
(> 2 3) = NIL.
(* 2 3) = 6.
(/ 2 3) = 2/3.
(EXPT 2 3) = 8.
(CAR '(2 3)) = 2.
(CDR '(2 3)) = (3).
(CONS 2 3) = (2 . 3).
NIL comme on le voit, il n'y a pas que des calculs qui sont effectués.
Changement de base:
Jusqu'à présent, nous avons travaillé en base 10 (système décimal). On le voit à l'aide de la variable *read-base*:
*read-base*
10
Pour changer de base:
(setf *read-base* 8)
8 rend la nouvelle base (en base 10)
15
13 15 en base 8 est auto-évalué à 13 (en base 10)
En base 8, 8 et 9 ne sont pas des nombres et ne sont donc pas auto-évalués:
8
; Evaluation aborted on #
on obtient une erreur
8 et 9 sont alors considérés comme des symboles: |8| et |9|
Donc, quand vous imposez la lecture en base 8, tout nombre formé des chiffres [0 1 2 3 4 5 6 7] est interprété en base 8, puis évalué en base 10.
Les opérations sont possibles:
(+ 10 5)
13 le résultat est exprimé en base 10
(+ 10 1)
9 Le résultat est bien en base 10 car 9 n'existe pas en base 8
(* 10 2)
16 En fait: 8x2 en base 10
Si on fait le calcul en base 8: 10x2=20, et la conversion en base 10 donne: 2x8^1+0x8^0=16.
Comme le résultat est rendu en base 10, parfois on ne voit pas de différence:
(expt 2 3)
8 2 et 3 sont bien interprétés en base 8, le calcul en base 8 donnerait 10.
Mais comme l'affichage est rendu en base 10: 1x8^1+0x8^0=8.
*read-base*
8 la valeur de *read-base* s'affiche aussi en base 10
Revenons à la base 10 (8+2, soit 10+2=12 en base 8)
(setf *read-base* 12)
10 rend la nouvelle base (10)
(* 10 2)
20 résultat correct en base 10
Conclusion: la lecture et l'évaluation se font dans la base contenue dans *read-base* et le résultat est exprimé en base 10.
Mais attention! Voyons ce qui se passe dans un LET:
(ceci suppose que vous êtes bien revenu dans la base 10, sinon 8 serait interprété comme un symbole dans le LET et ceci entraîne une erreur.)
(let ((*read-base* 8)) 9)
9 on s'attend à une erreur car 9 n'existe pas en base 8
mais 9 n'est pas lu en base 8! Il est évalué en base 10.
et c'est ce que rend le LET (voir la définition du LET: http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node83.html)
Essayons:
(let ((*read-base* 8)) (read))
9 la ligne au dessus attend une réponse: on entre 9 et...
|9| le symbole |9| est rendu (9 n'existe pas en base 8, c'est donc un symbole)
(let ((*read-base* 8)) (read))
14 on entre 14 et ...
12 on obtient 8+4. Cette fois il y a eu lecture et évaluation en base 8, puis affichage en base 10
On peut vouloir afficher un résultat dans une base spécifiée:
(setf *print-base* 2)
10 10 en base 2, c'est: 1x2^1+0x2^0=2 en base 10
D'ailleurs, quelque soit la base choisie, on obtient 10 (essayez avec 8)
5
101 5 est lu et évalué en base 10, puis affiché en base 2
(+ 4 5)
1001 le résultat de 4+5 est rendu en base 2
Pour revenir à l'affichage en base 10:
(setf *print-base* 10)
10 la base d'affichage revient à 10
(+ 4 5)
9 C'est bien le résultat en base 10
Compliquons un peu: on veut lire en base 2 et afficher en base 2!
(setf *read-base* 2)
2 pour lire en base 2
(setf *print-base* 10)
10 pour afficher en base 2
car 10 dans (setf *print-base* 10) est lu en base 2!
(+ 100 101)
1001 4+5 ça fait 9.
(setf *read-base* 1010)
1010 pour revenir à la lecture en base 10 (1010 en base 2)
(setf *print-base* 10)
10 pour revenir à l'affichage en base 10
(+ 4 5)
9 C'est bon!
La prochaine fois: "OPERATIONS LOGIQUES sur les nombres entiers."