.. _tut-nombres: *********** Les nombres *********** Une vidéo de présentation des nombres dans Python... .. raw:: html Les valeurs numériques ====================== Python permet de manipuler trois types numériques : les entiers (type :class:`int`), les réels (type :class:`float`) et les complexes (:class:`complex`). :class:`type` retourne le type de l'objet passé en argument:: >>> type(1) >>> type(1.0) >>> type(1+1j) En Python nul besoin de déclarer le type des objets manipulés. Python utilise un mécanisme d'`inférence de type `_. Lorsque plusieurs types différents sont mis en jeu, les opérations mathématiques standard retournent le résultat dans le type qui conserve la précision. Une opération mélant un :class:`int` et un :class:`float` retournera un :class:`float`:: >>> type(1 + 1.0) Une opération mélant un :class:`float` et un :class:`complex` retournera un :class:`complex`:: >>> type(1.0 + 1+2j) .. admonition:: A expérimenter... Utiliser l'interpréteur interactif pour vérifier votre intuition sur le résultat retourné par une opération arithmétique mélant un :class:`int` et un un :class:`complex`. Python est un langage entièrement objet. Chacun des objets numériques manipulé ci dessus est une instance d'une des classes :class:`int`, :class:`float` et :class:`complex`. Chacune de ces classes possède un constructeur portant le même nom que la classe. Celui ci a pour but, lorque c'est possible, de construire une instance de la classe à partir du paramètre passé en argument:: >>> int(1) 1 >>> int(1.0) 1 Lorsque l'opération n'est pas possible, une erreur se produit:: >>> int(1+1j) Traceback (most recent call last): File "", line 1, in TypeError: can't convert complex to int Les messages d'erreur de Python sont relativement explicites : - la dernière ligne fournit une explication en langage naturel ; - l'avant dernière ligne localise l'erreur dans la séquence d'instructions exécutées. La précision ------------ Contrairement à d'autres langages la précision des entiers et des rééls n'est pas fixée a priori mais adaptée dynamiquement aux résultats du calcul et ne dépend que de la taille de la mémoire. L'ensemble des modules disponibles n'est pas chargé en mémoire au démarrage de l'interpréteur pour des raisons évidentes de performances (temps de chargement et d'occupation mémoire). Seuls les modules de base sont importés au démarrage de Python. Si d'autres sont nécessaires, on les importe à la demande. Le module :mod:`math` fait partie de ceux là. Il est chargé avec l'instruction :keyword:`import`:: >>> import math Ce module dispose d'un grand nombre de fonctions disponibles, en particulier la fonction :func:`~math.factorial`:: >>> math.factorial(5) 120 Parce que les nombres sont codés sur un nombre statique de bits, la plupart des langages ne peuvent pas calculer de factorielle supérieure à 20 (:math:`20 ! = 2432902008176640000`) sans utiliser de modules spécialisés. Python utilise dynamiquement la mémoire pour allouer la taille nécessaire :: >>> math.factorial(50) 30414093201713378043612608166064768844377641568960512000000000000 Bien qu'il n'y ait absolument aucun intérêt à manipuler un si grand nombre, pour illustrer ce phénomène :: >>> math.factorial(500) 1220136825991110068701238785423046926253574342803192842192413588385845373153881997605496447502203281863013616477148203584163378722078177200480785205159329285477907571939330603772960859086270429174547882424912726344305670173270769461062802310452644218878789465754777149863494367781037644274033827365397471386477878495438489595537537990423241061271326984327745715546309977202781014561081188373709531016356324432987029563896628911658974769572087926928871281780070265174507768410719624390394322536422605234945850129918571501248706961568141625359056693423813008856249246891564126775654481886506593847951775360894005745238940335798476363944905313062323749066445048824665075946735862074637925184200459369692981022263971952597190945217823331756934581508552332820762820023402626907898342451712006207714640979456116127629145951237229913340169552363850942885592018727433795173014586357570828355780158735432768888680120399882384702151467605445407663535984174430480128938313896881639487469658817504506926365338175055478128640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 Pour ce qui concerne les nombres réels, les plus petit et plus grand nombres que l'on peut représenter dépendent de la machine et du système d'exploitation utilisé. Cette information nécessite l'utilisation du module :mod:`sys`:: >>> import sys >>> print(sys.float_info) sys.float_info(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308, min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15, mant_dig=53, epsilon=2.220446049250313e-16, radix=2, rounds=1) Le nombre d'atomes dans l'univers observable est communément estimé autour de :math:`10^{80}`. La limite imposée par Python ne devrait donc pas être un problème ! Cependant les problèmes de `représentation des nombres réels `_, ainsi que de la précision des calculs qui en découle sont bien connus. La représentation n'est exacte que lorsque le nombre réel est décomposable en une somme de puissances de 2. La représentation utilisée par la machine est donnée par le module :mod:`decimal`:: >>> from decimal import Decimal >>> 0.5 0.5 >>> Decimal(0.5) # 0.5 = 2**(-1), la représentation est exacte Decimal('0.5') Lorsque le nombre n'est pas décomposable en une somme de puissances de 2, la représentation est approchée:: >>> 0.1 0.1 >>> Decimal(0.1) # 0.1 n'est pas décomposable en une somme de puissances de 2, la représentation est approchée Decimal('0.1000000000000000055511151231257827021181583404541015625') Cette représentation approchée des nombres réels doit être connue pour ne pas prendre de décision incorrecte, en particulier lors des tests d'égalité. Observons l'exemple ci dessous:: >>> 0.1 + 0.1 + 0.1 0.30000000000000004 Le résultat de la somme de nombres réels dont la représentation est approchée, est lui même une valeur approchée. En conséquence un test d'égalité naïf échoue:: >>> 0.1 + 0.1 + 0.1 == 0.3 False Les tests d'égalité entre valeurs réelles doivent utiliser des valeurs arrondies:: >>> round(0.1 + 0.1 + 0.1, 10) == round(0.3, 10) True Pour ce qui concerne l'affichage, la console Python affiche 16 chiffres après la virgule:: >>> x = 1.2000000000000001 >>> x 1.2000000000000002 >>> Decimal(x) Decimal('1.20000000000000017763568394002504646778106689453125') Au delà, la valeur arrondie est utilisée pour l'affichage, mais la représentation en machine conserve la précision:: >>> x = 1.20000000000000001 >>> x 1.2 >>> Decimal(x) Decimal('1.1999999999999999555910790149937383830547332763671875') Les nombres sont des objets --------------------------- Tout étant objet en Python, chaque classe numérique possède ses propres méthodes. Une façon rapide d'identifier les méthodes disponibles sur un type donné est d'appeler la fonction :func:`dir` sur cet objet:: >>> dir(1) [..., 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes'] La liste retournée est beaucoup plus longue. Les méthodes préfixées par ``__`` sont des méthodes internes dont l'utilisation sera abordée dans la programmation objet. Elles sont appelées *dunder methods* (double underscore). Parmi les autres, certaines ne présentent pas un grand intérêt (pour le moment). Elles permettent cependant de doter les :class:`int` d'une interface commune avec les :class:`float`. Parmi les méthodes intéressantes, on peut connaître la dimension de la représentation binaire d'un :class:`int` avec la méthode :meth:`int.bit_length` >>> (15).bit_length() 4 ou la représentation hexadécimale avec la méthode :meth:`int.to_bytes`:: >>> (15).to_bytes(1, byteorder='big') b'\x0f' >>> (15).to_bytes(2, byteorder='big') b'\x00\x0f' Ces opérations là étant courantes, elles existent aussi sous la forme de fonctions *built in*. En Python l'idiome ``objet.methode()`` n'est pas gravé dans le marbre. Certaines opérations courantes peuvent être également effectuées avec la syntaxe ``fonction(paramètres)``. Les fonctions :func:`bin` et :func:`hex` en sont un exemple:: >>> bin(15) '0b1111' >>> hex(15) '0xf' Cependant, en sous main, la plupart des fonctions de Python appellent une méthode de la classe de l'objet passé en paramètre. C'est une fonctionnalité puissante qui permet d'utiliser une même syntaxe, quel que soit l'objet (pour peu que celui ci implémente la méthode). On pourra par exemple obtenir la dimension d'un objet avec la fonction :func:`len`, que cet objet soit une chaîne de caractères, une liste, un dictionnaire, ... Les nombres réels ont des méthodes qui leur sont propres:: >>> dir(1.0) [..., 'as_integer_ratio', 'conjugate', 'fromhex', 'hex', 'imag', 'is_integer', 'real'] Le rôle de la méthode :meth:`~float.is_integer` est trivial:: >>> (2.0).is_integer() True >>> (2.5).is_integer() False :meth:`as_integer_ratio` renvoie deux entiers dont le rapport est la représentation exacte de l'argument, c'est à dire celle donnée par le module :mod:`decimal`:: >>> (2.5).as_integer_ratio() (5, 2) >>> 443/37 # représentation approchée 11.972972972972974 >>> Decimal(443/37) # représentation exacte Decimal('11.9729729729729736931176375946961343288421630859375') >>> (443/37).as_integer_ratio() (6740184577449763, 562949953421312) >>> 6740184577449763/562949953421312 == 443/37 True >>> 6740184577449763/562949953421312 # représentation approchée 11.972972972972974 >>> Decimal(6740184577449763/562949953421312) # représentation exacte Decimal('11.9729729729729736931176375946961343288421630859375') Dans l'immense majorité des cas, une valeur arrondie des nombres réels est suffisante. Sinon, la méthode :meth:`as_integer_ratio` est utilisée pour manipuler la valeur originale sans perte de précision. Les nombres complexes ont également des méthodes qui leur sont propres:: >>> dir(1+1j) [..., 'conjugate', 'imag', 'real'] >>> (1+1j).conjugate() (1-1j) >>> (1+1j).real 1.0 >>> (1+1j).imag 1.0 .. Warning:: :func:`dir` retourne une liste des méthodes et des attributs. La distinction entre les deux n'apparait pas dans la liste retournée. Il faut jeter un oeil à la documentation. Les opérations arithmétiques ---------------------------- Les opérations arithmétiques sont disponibles dans la console, ce qui permet d'utiliser Python comme une calculette scientifique:: >>> 11 + 3 14 >>> 11 - 3 8 >>> 11 * 3 33 >>> 11 / 3 # division réelle 3.6666666666666665 >>> 11 // 3 # division entière 3 >>> 11 % 3 # modulo 2 >>> divmod(11,3) # division entière + modulo (3, 2) >>> 11 ** 3 # exponentiation 1331 La variable spéciale ``_`` contient le dernier résultat:: >>> 5/2 2.5 >>> _ 2.5 Les variables ============= L'instruction d'affectation, comme dans la plupart des autres langages, est le signe ``=``. L'accès à une variable non utilisée produit une erreur:: >>> a = 3 >>> b = 4 Dans l'interpréteur l'évaluation d'une variable retourne sa propre valeur:: >>> a 3 L'instruction ci dessous produit un résultat similaire, mais le mécanisme est différent. :func:`print` retourne une chaîne de caractère, et c'est cette chaîne de caractère qui est évaluée:: >>> print(a) 3 Lorsque les instructions seront groupées dans un fichier pour être exécutées en bloc, l'affichage nécessitera l'utilisation de :func:`print`. Une tentative d'accès à une variable non initialisée déclenche un message d'erreur dont l'explication est fournie sur la dernière ligne :: >>> c Traceback (most recent call last): File "", line 1, in NameError: name 'c' is not defined L'affectation multiple est possible en Python:: >>> a, b = 3, 4 >>> a 3 >>> b 4 En Python, il n'y a pas de phase de compilation comme en Java ou en C. Le type des variables n'est pas défini de façon statique. Une même variable peut donc faire référence à des objets différents au cours de l'exécution d'un programme:: >>> a = 3 >>> type(a) >>> a = "hello" >>> type(a) On dit que Python est un langage fortement typé (une variable possède toujours un type objet) et un langage typé dynamiquement (le type affecté à une variable peut changer au cours de l'exécution du programme). Les booléens ============ La notion de vérité ------------------- On appelle prédicat une expression pouvant seulement prendre les valeurs booléennes ``True`` ou ``False``. Les prédicats sont utilisés pour les tests logiques employés par exemples dans les structures de contrôle :keyword:`if` et :keyword:`while` abordées plus tard. Par rapport à d'autres langages, Python a une vision élargie de la **vérité** et considère ``False`` les objets suivants: - ``None`` ; - ``False`` ; - les numériques de valeur nulle : ``0``, ``0.0``, ``0j`` ; - tout objet **vide**, c'est à dire de taille nulle. La fonction :class:`bool` permet d'illustrer ce comportement:: >>> bool(None) False >>> bool(False) False >>> bool(0+0j) False >>> bool('') False Par extension, les autres objets sont ``True``: - ``True`` ; - les numériques de valeur non nulle ; - tout objet non **vide**, c'est à dire de taille non nulle. Quelques exemples:: >>> bool(True) True >>> bool(1) True >>> bool('a') True Les opérateurs booléens ----------------------- Les opérateurs classiques :keyword:`and`, :keyword:`or` et :keyword:`not` sont disponibles. Leur comportement est le suivant (pseudo langage): - x or y : x is false, then y, else x - x and y : x is false, then x, else y - not x : x is false, then True, else False Pour l'opérateur :keyword:`and`:: >>> True and False False >>> True and True True >>> False and True False >>> False and False False Pour l'opérateur :keyword:`or`:: >>> True or False True >>> True or True True >>> False or True True >>> False or False False Pour l'opérateur :keyword:`not`:: >>> not True False >>> not False True Les opérateurs booléens ont tous la même priorité. Utiliser les parenthèses pour les groupements:: >>> True and False False >>> not True and False False >>> (not True) and False False >>> not (True and False) True L'évaluation des opérateurs logiques se fait de façon paresseuse (lazy). Ci dessous ``a`` n'est jamais évaluée:: >>> False and a False De même, dans l'expression ci dessous ``b`` n'est jamais évaluée:: >>> True or b True Au delà des simples opérateurs :keyword:`and`, :keyword:`or` et :keyword:`not`, d'autres opérateurs logiques sont disponibles dans le module :mod:`operator`. Par exemple l'opérateur "ou exclusif" :func:`~operator.xor`:: >>> from operator import xor >>> xor(0,0) 0 >>> xor(0,1) 1 >>> xor(1,0) 1 >>> xor(1,1) 0 Ce qu'il faut retenir ===================== .. quiz:: quizz-02 :title: Les nombres - En Python les nombres entiers sont représentés par la classe - En Python les nombres rééls sont représentés par la classe - En Python les nombres complexes sont représentés par la classe - :quiz:`{"type":"TF","answer":"F"}` Une opération mathématique nécessite des nombres de même type - :quiz:`{"type":"TF","answer":"T"}` Python dispose de mécanismes permettant de transformer le type d'un nombre - :quiz:`{"type":"TF","answer":"F"}` La transformation de type est toujours possible - :quiz:`{"type":"TF","answer":"F"}` Les nombres entiers sont codés sur 32 bits - :quiz:`{"type":"TF","answer":"F"}` Les nombres rééls peuvent être représentés avec une précision arbitrairement grande - :quiz:`{"type":"TF","answer":"F"}` L'égalité entre deux nombres rééls est donnée par l'opérateur ``==`` - :quiz:`{"type":"TF","answer":"T"}` Un nombre est un objet et dispose donc de méthodes - :quiz:`{"type":"TF","answer":"F"}` L'opérateur de division entière est ``/`` - :quiz:`{"type":"TF","answer":"T"}` Si ``a`` et ``b`` sont des entiers, le reste de la division de ``a`` par ``b`` peut s'obtenir avec ``divmod()`` - :quiz:`{"type":"TF","answer":"T"}` Si ``a`` et ``b`` sont des entiers, le reste de la division de ``a`` par ``b`` peut s'obtenir avec ``%`` - :quiz:`{"type":"TF","answer":"T"}` L'opérateur d'exponentiation est ``**`` - :quiz:`{"type":"TF","answer":"T"}` Utiliser une variable permet de stocker un résultat pour réutilisation ultérieure