Parcours
/
Julia
Julia
/
Programme
/
Les nombres
Le

Les nombres en Julia

7 exercices

À propos de Les nombres

Julia est un langage généraliste, qui peut servir à la plupart des tâches de programmation. En pratique, cependant, ses principaux cas d'usage se situent plutôt dans l'ingénierie et les sciences. Des calculs numériques rapides, polyvalents et sophistiqués sont au cœur de sa conception.

Les entiers

Un entier est un nombre « rond », sans virgule.

Dans le concept Bases, on a vu qu'une valeur entière peut être affectée à une variable sans préciser de type.

Pour la lisibilité, on peut utiliser des tirets bas comme séparateur de chiffres. Ils sont ignorés par le compilateur.

julia> x = 3
3

julia> typeof(x)
Int64

julia> large_number = 1_234_567_890
1234567890

En interne, le compilateur utilisera le type entier signé le plus approprié au processeur. Sur les PC modernes, ce sera généralement Int64, ce qui est parfaitement suffisant pour la plupart des tâches.

Les types seront abordés plus en détail dans un concept futur. Pour l'instant, les types numériques devraient être assez intuitifs pour que ce document ait du sens.

Comme nous le verrons dans un concept ultérieur, Julia prend nativement en charge des tableaux multidimensionnels très grands. Ceux-ci peuvent mettre à rude épreuve aussi bien la mémoire disponible que la bande passante pour les transferts de données.

Pour donner au programmeur un contrôle plus fin, il est à la fois possible et assez courant de préciser le type entier signé ou non signé souhaité. Les nombreux apprenants de Julia qui ont déjà utilisé Python voudront peut-être comparer cela à NumPy, plutôt qu'au Python de base.

julia> y = Int8(42)
42

julia> typeof(y)
Int8

julia> z = UInt32(1024)
0x00000400

Note que Julia affiche par défaut les entiers non signés au format hexadécimal, car les types non signés servent souvent à des opérations sur les bits de bas niveau.

Les entiers peuvent aussi s'écrire en binaire, en octal ou en hexadécimal, avec les préfixes 0b, 0o et 0x respectivement.

julia> a = 0x10
0x10

julia> Int(a)
16

julia> b = 0b1010 # displays as hexadecimal
0x0a

Débordements d'entiers et BigInt

Chaque type entier a une valeur maximale et une valeur minimale qu'il peut stocker :

julia> typemax(Int8)
127

julia> typemin(Int8)
-128

Sortir de cette plage valide provoquera un débordement d'entier, avec des résultats qui semblent très étranges.

julia> c = Int8(126)
126

julia> c * c
4

On peut sans doute convenir que 126 * 126 ne devrait pas être égal à 4 !

Une explication détaillée demanderait de savoir comment les registres du processeur stockent les entiers, ce qui dépasse le cadre de ce concept.

On peut minimiser le problème en utilisant un type « plus large », jusqu'à Int128 ou UInt128 (la signification de « plus large » est abordée ci-dessous). Cependant, tous ont des limites strictes avant de rencontrer des problèmes de débordement.

Une solution consiste à utiliser le type BigInt, qui n'est limité que par la mémoire de ton ordinateur. C'est l'implémentation par Julia de l'arithmétique à précision arbitraire.

julia> 2 ^ 70
0

julia> big = BigInt(2) ^ 70
1180591620717411303424

julia> UInt128(big)
0x00000000000000400000000000000000

Comme 2 puissance 70 ne tient pas dans un Int64, le premier calcul de l'exemple ci-dessus échoue : note cependant qu'il n'y a aucun message d'erreur. Programmeur, méfiance !

Convertir 2 en BigInt donne la bonne réponse, et l'afficher en hexadécimal montre plus clairement qu'il s'agit d'une puissance de 2.

Nombres à virgule flottante

Il n'est pas surprenant que les nombres à virgule flottante puissent avoir une virgule, et une partie fractionnaire après cette virgule.

julia> f = 3.45
3.45

julia> typeof(f)
Float64

Bien sûr, la notation scientifique est prise en charge.

julia> avogadro = 6.02e23
6.02e23

Comme pour les entiers, le type par défaut convient à la plupart des usages, mais d'autres types signés sont disponibles. Il n'existe pas de type à virgule flottante non signé.

Pour faire plus court, on peut créer des valeurs Float32 en utilisant le suffixe f0.

julia> f32 = 4.56f0
4.56f0

julia> typeof(f32)
Float32

Les autres tailles de flottants nécessitent une conversion explicite, comme pour les entiers.

Les valeurs maximale et minimale peuvent surprendre :

julia> typemax(Float64)
Inf

julia> typemin(Float64)
-Inf

L'infini est une valeur valide !

Cependant, la plage utile des nombres à virgule flottante est limitée : tout ce qui est très grand se voit simplement attribuer une valeur Inf, et tout ce qui est très petit est arrondi à 0.0.

On peut utiliser une autre paire de fonctions pour visualiser ces limites : environ ± 10 ^ 308.

julia> floatmax(Float64)
1.7976931348623157e308

julia> floatmin(Float64)
2.2250738585072014e-308

De plus, la précision est limitée : un nombre grand mais fini de chiffres significatifs est représenté (environ 15 pour Float64).

Opérateurs arithmétiques

Comme nous l'avons vu dans le concept Bases, les opérateurs arithmétiques fonctionnent pour l'essentiel comme l'arithmétique classique, telle qu'on l'enseigne aux enfants. Note que l'exponentiation utilise ^, et non ** (les deux sont courants dans d'autres langages).

2 + 3  # 5 (addition)
2 - 3  # -1 (subtraction)
2 * 3  # 6 (multiplication)
8 / 2  # 4.0 (division)
8 % 3  # 2 (remainder)
2 ^ 3  # 8 (exponentiation)

Cependant, quelques détails propres à Julia méritent d'être examinés.

Multiplication

julia> x = 4.2
4.2

julia> 2 * x
8.4

julia> 2x
8.4

julia> 2.4x
10.08

Cela peut surprendre.

Il est toujours possible d'utiliser * comme opérateur infixe, comme dans la plupart des autres langages informatiques.

Cependant, Julia a été conçu par des gens qui pensent que le code devrait ressembler autant que possible à des équations mathématiques.

Comme les noms de variables doivent commencer par une lettre, faire précéder le nom d'un nombre (entier ou à virgule flottante) est interprété comme une multiplication implicite.

Par exemple, si on veut l'aire d'une sphère, au lieu de 4 * pi * r * r, on pourrait écrire ceci :

julia> surface(r) = 4π * r^2
surface (generic function with 1 method)

julia> surface(3)
113.09733552923255

Bien que π soit une constante intégrée, c'est aussi une lettre (grecque). L'analyseur syntaxique a donc toujours besoin d'un * explicite pour séparer π de r.

Division

Utiliser / comme opérateur infixe donnera toujours un résultat à virgule flottante, même pour des entrées entières.

Pour la division entière, il existe plusieurs options :

julia> 10 / 3
3.3333333333333335

julia> div(10, 3)
3

julia> 10 ÷ 3
3

julia> 10 // 3
10//3

La fonction div() sert à la division entière, le résultat étant tronqué vers zéro : vers le bas pour les nombres positifs, vers le haut pour les nombres négatifs.

Comme synonyme, on peut utiliser l'opérateur infixe ÷, là encore pour rendre le tout plus mathématique. Si tu utilises un éditeur compatible avec Julia, saisis-le avec \div puis appuie sur la touche <Tab>.

L'opérateur // aura besoin d'un concept qui lui soit propre, plus tard dans le programme.

Pour l'instant, disons simplement que le résultat de // est un nombre « rationnel », le nom formel de ce que la plupart des gens appellent une fraction.

Les facteurs communs sont retirés du numérateur et du dénominateur, pour donner un rapport de deux entiers sous ce qu'on appelle la « forme irréductible ».

julia> rationalnum = 22 // 6
11//3

julia> typeof(rationalnum)
Rational{Int64}

On a les nombres rationnels. Et les nombres « irrationnels » ?

julia> π
π = 3.1415926535897...

julia> typeof(π)
Irrational{:π}

Un nombre irrationnel est un nombre qui ne peut pas être réduit à un rapport d'entiers. Les exemples courants incluent π, e (nombre d'Euler), et de nombreuses racines comme √2 (la racine carrée de 2).

Julia essaie de faire des mathématiques correctement.

Conversion des types numériques

Cela peut souvent se produire automatiquement :

julia> x = 2 + 3.5
5.5

julia> typeof(x)
Float64

On a additionné un Int64 et un Float64, et obtenu un résultat Float64.

En fait, l'entier a été converti silencieusement en Float64 avant l'addition.

Julia a une notion de « largeur » des types numériques.

  • Entre entiers, et entre flottants, il s'agit simplement du nombre de bits nécessaires au stockage. Ainsi, Int64 est plus large que Int16.
  • Les flottants sont considérés comme plus larges que les entiers, car ils peuvent stocker une partie fractionnaire.

Si une expression mélange plusieurs types, chacun est « promu » si nécessaire vers le type le plus large utilisé.

Pour forcer la conversion, on peut convertir un entier vers un type précis, comme dans Float64(5).

Sinon, il suffit d'utiliser float(5) et de laisser le compilateur choisir un type approprié.

Les conversions d'un flottant vers un entier sont forcément plus compliquées. Que veux-tu faire de ce qui suit la virgule ?

  • La fonction round() convertit vers l'entier le plus proche, les valeurs à mi-chemin comme 4.5 étant arrondies vers l'entier pair le plus proche.
  • floor() arrondit vers le bas, ceil() vers le haut, trunc() vers zéro.
  • Tenter de convertir directement, par exemple avec Int32(), échouera avec une InexactError.

Cependant, par défaut, ces fonctions ne renvoient pas le type entier que tu aurais peut-être souhaité. Le type de sortie souhaité peut être précisé.

julia> round(4.5)
4.0

julia> round(Int64, 4.5)
4

julia> round(Int, 4.5)  # => default integer type
4

julia> ceil(Int16, 4.3)
5

Arrondir à un nombre précis de chiffres après la virgule est aussi possible avec le mot-clé digits.

julia> round(π, digits=10)
3.1415926536

Voir le manuel pour plus de détails.

Division par zéro

Ça lève simplement une erreur, non ? En fait, la situation n'est pas si simple.

La division entière avec ÷ ou // donnera une erreur, comme on peut s'y attendre.

La division à virgule flottante avec / adopte ce qu'on pourrait considérer comme une approche d'ingénieur, plutôt qu'une approche informatique classique :

julia> 2 / 0
Inf

julia> 0 / 0
NaN

Comme nous l'avons vu dans une section précédente, l'infini est un nombre à virgule flottante valide en Julia, représenté par Inf.

Quand le numérateur est lui aussi nul, le résultat est mathématiquement indéfini. Julia le traite alors comme un « n'est pas un nombre », représenté par NaN.

Si cela te semble étrange, pense au contexte du travail sur de grands tableaux de données du monde réel (et donc souvent assez désordonnées). Pour avancer, le mieux est de simplement signaler les valeurs problématiques et de passer à la suite.

Une vérification manuelle sans fin des valeurs serait pénible à programmer, et nuirait à coup sûr aux performances d'exécution.

S'arrêter avec un message d'erreur à la moindre anicroche rendrait ton programme très impopulaire auprès des utilisateurs !

Comparer des valeurs à virgule flottante

Comme décrit dans le concept Conditionals, le test d'égalité se fait généralement avec l'opérateur ==.

Cela fonctionne bien pour les entiers, les caractères, les chaînes de caractères, etc. Cependant, les valeurs à virgule flottante ont une précision limitée, et différentes façons de calculer un même résultat peuvent mener à des valeurs légèrement différentes. Pour Float64, ce sera typiquement autour du 15e chiffre significatif : une petite différence, mais pas une « égalité ».

Traditionnellement, le conseil donné aux programmeurs est de ne jamais utiliser == avec des valeurs à virgule flottante : les résultats sont imprévisibles.

Une alternative très répandue consiste à comparer la valeur absolue de la différence à une tolérance autorisée (souvent appelée epsilon ou ϵ). Ainsi, au lieu de a == b, on utilise abs(a - b) < epsilon.

Julia propose une alternative plus propre avec la fonction isapprox().

Pour une tolérance absolue, la syntaxe est isapprox(a, b, atol=epsilon). Le mot-clé atol est requis dans ce cas.

La tolérance relative est souvent plus utile. C'est la valeur par défaut, donc isapprox(a, b) essaiera de choisir une valeur raisonnable pour rtol (les règles sont assez compliquées).

On peut aussi préciser une tolérance relative, sous forme de fraction des valeurs comparées. Ainsi, isapprox(a, b, rtol=0.01 vérifie que les valeurs sont à moins de 1 % l'une de l'autre.

Comme souvent avec Julia, il existe un opérateur mathématique synonyme du cas par défaut : a ≈ b (l'opérateur s'écrit \approx puis <tab>).

Concepts futurs associés

En plus des nombres rationnels, les parties suivantes du programme aborderont :

Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet

Apprends Les nombres

L'entraînement est verrouillé

Déverrouille 2 exercices de plus pour t'entraîner sur Les nombres