Valeurs entières

images/whitebelt.png

Utilisons Jshell en mode ligne à ligne et écrivons des valeurs littérales de nombres entiers.

Quand on lance jshell de cette manière une invite (jshell> ) s’affiche en début de ligne.

jshell> 666
$1 ==> 666

jshell> -33
$2 ==> -33

Dans ce cas (très particulier!) Jshell nous affiche le résultat de la ligne et lui affecte un numéro ($1, $2, etc.). (nous verrons plus tard que ceci est spécifique à ce mode de Jshell: une telle ligne de code esseulée ne sera pas légale en Java standard).

Opérations

Bien entendu on utilisera des opérations sur les nombres entiers.

(Dans le code tout ce qui suit la paire // jusqu'à la fin de la ligne est un commentaire: l’exécuteur ne les prend pas en compte)

jshell> 666 + 333 //addition
$3 ==> 999

jshell> 666 - 999 // soustraction
$4 ==> -333

jshell> 666 * 2 // multiplication
$5 ==> 1332

jshell> 666 / 2 // division entière
$6 ==> 333

jshell> 666 %10 // modulo (reste de la division)
$7 ==> 6

Ici les conventions du langage ne sont pas celles que nous utilisons habituellement pour noter des opérations (par exemple: le caractère * pour la multiplication ou le caractère / pour noter la division).

La situation se complique si on veut écrire des expressions complexes. Quel sera la résultat de l’expression 3*7+2 ?

jshell> 3*7+2
$8 ==> 23

Ici il y a application d’une règle de précédence (les opérateurs "multiplicatif" - *, / , % - on précédence sur les opérateurs additifs -plus ou moins-).

Notre conseil: tentez d’oublier les règles de précédence et utilisez des parenthèses pour écrire exactement ce que vous voulez faire!

Donc:

jshell> (3*7)+2 // plus lisible
$9 ==> 23

jshell> 3*(7+2) // si c'est ce que vous voulez
$10 ==> 27

(bien sûr dans cet exemple 7+2 est évident mais avec des calculs complexes ce n’est plus aussi clair!)

Limites

images/orangebelt.png

Faisons un essai de représentation d’un grand nombre:

jshell> 107374757677
|  Error:
|  integer number too large: 107374757677
|  107374757677
|  ^

entiers

Ici il faut avoir conscience de plusieurs choses:

  • Les littéraux entiers tels que nous les avons utilisés représentent des données d’un type primitif scalaire en Java. Ces données sont connues sous le nom de integer (et oui tout est en anglais!) -Nous verrons ultérieurement que ce type se note int.
  • Les données de type primitif sont censées être proches de ce que manipule directement le processeur de votre machine. On a donc un choix de donnée lié à une recherche de performance: en étant proche de la machine on optimise les opérations.
  • Le processeur manipule des données binaires … dans une plage étroite. Ici le processeur de référence est un processeur 32 bits.

    Petits rappels sur le binaire: wikipédia: système binaire (bien regarder en particulier la partie "complément à deux" pour comprendre la représentation interne des nombres négatifs).

    Donc les integers natifs de Java représentent des valeurs comprises entre -231 et 231-1 (pour les débutants: ici essayer de comprendre le pourquoi de cette plage de valeurs).

    Au niveau le plus bas on a l’habitude de considérer que la représentation binaire se fait sur 4 octets (blocs de 8 bits).

  • Le processeur de référence connu par java n’est peut-être pas exactement celui qui anime votre machine. Votre processeur utilise peut-être directement des données sur 64 bits … mais, surtout, peut ne pas avoir un arrangement des octets qui soit celui géré par Java.

    Il y a des processeurs "gros-boutiens" (big-endian) et des processeurs "petit-boutiens" (little_endian) : wikipédia: ordre des octets.

    Par exemple: représentation portable int sur 4 octets est poids/fort-poids/faible avec un codage en complément à deux pour les nombres négatifs (complément à un plus 1 ):

    poids fort                poids faible
    
    0000 0000   0000 0000     0000 0000   0000 0001    -> 1
    0000 0000   0000 0000     0000 0000   0000 0010    -> 2
    0000 0000   0000 0000     0000 0000   1111 1111    -> 255
    
    1111 1111   1111 1111     1111 1111   1111 1111    -> -1
    1111 1111   1111 1111     1111 1111   1111 1110    -> -2
    1111 1111   1111 1111     1111 1111   0000 0001    -> -255

    Cette différence de représentation a posé des problèmes. Dans certains langages (comme le langage C) l’utilisation directe de données natives posait des problèmes de portabilité: si on transférait des entiers natifs d’une machine à une autre on risquait de ne pas obtenir les mêmes valeurs! Un des objectifs majeurs de Java est la portabilité: toute donnée doit être indépendante de l’architecture de la machine.

    Java utilise donc une représentation proche du processeur mais qui n’est pas nécessairement exactement celle de ce processeur. (cette représentation a des raison historiques: c’est celle des processeurs SPARC 32 bits qui étaient utilisés sur les machines SUN au moment de la création de Java. C’est une représentation "gros-boutienne")

    Donc point à retenir: les données en java sont portables. On peut transférer, par exemple, des entiers entre des machines très différentes et on obtiendra toujours les mêmes valeurs.

Explorons un petit plus précisément les propriétés de cette donnée native:

jshell> 1073747576 + 1073747576
$1 ==> -2147472144

Surprise!: on additionne ici deux nombres positifs … et on obtient un nombre négatif!

Explications:

  • Une addition de ces deux nombres aurait donné une valeur dépassant le nombre entier maximum représentable sur 32 bits (4 octets).
  • En fait quand on opère sur ces données de niveau processeur (ces données binaires) l’opération addition effectue des manipulations de bits … et ici les bits obtenus correspondent à une valeur négative!

Les valeurs littérales de type primitif nous donnent accès en fait à des données binaires de bas niveau. Nous pouvons les utiliser pour manipuler des nombres entiers à condition de rester dans une plage de valeurs donnée. Ces données binaires peuvent être utilisées à d’autres fins (par exemple pour représenter des couleurs!).

Ceci illustre bien aussi le fait qu’un langage de programmation vous offre un certain nombre de services qu’il faut bien connaître (les spécifications). Il est imprudent de s’imaginer qu’on obtiendra des résultats sur la seule base de nos intuitions.

Prenons un autre exemple: les propriétés de la division sur ces données entières.

jshell> 100/3
$1 ==> 33

Ici la division opère dans un ensemble bien défini: les deux membres de l’opération sont du type primitif scalaire int et le résultat est également de ce type (on n’obtient pas quelque chose qui ressemblerait à 33,333!). L’opération est nommée division entière.

Encore une précision:

jshell> 100/0
|  java.lang.ArithmeticException thrown: / by zero
|        at (#2:1)

La division entière par zéro est une opération interdite!

[Note]Question

Peut-on manipuler des nombres entiers en dehors de la plage des int (entre -231 et 231-1 )?

Oui si besoin est: nous verrons ultérieurement les types long et java.math.BigInteger

--exercices-- : expérimentez! Avec Jshell en mode ligne à ligne manipulez des valeurs entières et exécutez des opérations et des combinaisons d’opérations de plus en plus complexes.