4. Fonctions mathématiques

4.1. Fonctions usuelles

Les fonctions utiles aux mathématiques sont réparties en plusieurs endroits de la bibliothèque standard. Il existe une classe java.lang.Math, elle ne contient que des méthodes et des constantes ( E et PI) statiques. On y trouve toutes les fonctions mathématiques usuelles : conversions d’angles, trigonométrie, logarithmique, valeur absolue, max et min, racine carrée, calculs de puissance. On y trouve aussi un générateur de nombres aléatoires. Dans la mesure où toutes ces méthodes sont statiques, il n’est pas besoin de créer un objet de la classe Math pour invoquer ses méthodes. Voici un exemple de calcul d’un sinus.

Exemple 71. Utilisation de la classe Math

double a = Math.PI/5 ;  
 double sina = java.lang.Math.sin(a) ;

4.2. Générateurs aléatoires

Comme la plupart des langages, Java propose un générateur de séries aléatoires. La classe de base est java.util.Random, et son contrat est très précis. Afin, une fois de plus, de garantir la portabilité des résultats de calcul, l'algorithme de génération de séries aléatoires est spécifié pour cette classe. Deux JVM différentes, sur deux OS différents, génèrent donc les mêmes séries aléatoires. Rien n'empêche bien sûr de créer d'autres générateurs, et le JDK propose une classe SecureRandom, dont la génération est plus coûteuse, mais aussi moins prédictible. Construire une série aléatoire se fait en créant un objet de la classe Random. Jusque là rien d'inhabituel. On peut instancier cette classe en passant un paramètre au constructeur, appelé "graine" ( seed en anglais). Tirer des nombres aléatoires se fait ensuite par appel d'une des méthodes de Random : nextInt() pour obtenir un int, nextFloat() pour un float (tous les types de base numériques sont supportés), ou nextInt(int n) pour obtenir un entier compris entre 0 et n - 1. Deux objets construits à partir de la même graine vont générer la même série aléatoire, ce qui signifie que les appels successifs à nextInt() (par exemple) vont générer les mêmes entiers. On rencontre très fréquemment le même bug lors de l'utilisation de la classe Random. Examinons le code suivant.

Exemple 72. Première utilisation de la classe Random (incorrecte !)

public  int getRandomInt(int limite) {  
     return (new Random()).nextInt(limite) ;  
}  
  
 // ailleurs  
 public  void uneMethode() {  
     int k = getRandomInt(10) ;  
}

Le problème de ce code est qu'il ne génère pas une vraie série aléatoire. Plus la machine sur laquelle ce code est executé est puissante, plus les appels à la méthode getRandom(int) sont rapprochés dans le temps, et moins la série générée sera aléatoire. Quel est le (gros) problème ? L'initialisation de notre objet Random est refaite à chaque appel de getRandom(int), et ne reçoit pas de graine. Le JDK en construit donc une lui-même, à partir de l'heure courante. La probabilité que deux appels consécutifs à getRandom(int) génèrent la même graine est d'autant plus importante que les deux appels consécutifs sont proches dans le temps. Si c'est le cas, notre série aléatoire va se mettre à bégayer, et générer le même nombre plusieurs fois de suite. Ce type de bug est très difficile à détecter, car notre générateur aléatoire continue à avoir une moyenne et un spectre corrects. Pour détecter qu'il n'est plus aléatoire, il faut calculer les probabilités de transition, c'est-à-dire les "chances" que l'on a de tirer deux fois de suite la même valeur, rapporté aux transitions. On peut le voir sur l'exemple suivant, qui consiste à observer le comportement d'une variable aléatoire entière qui tire des 0 et des 1.

Exemple 73. Mise en évidence d'une mauvaise utilisation de Random

public  class TestRandom {  
  
     public  static  int getRandomInt(int limite) {  
         return (new Random()).nextInt(limite) ;  
    }  
  
     public  static  void main(String[] args) {  
         int proba[][] =  new  int [2][2] ;  
      
         int i = getRandomInt(2) ;  
         int j =  0 ;  
      
         int n =  1000000 ;  
         for (int k =  0 ; k < n ; k++) {  
            j = getRandomInt(2) ;  
            proba [i][j] ++ ;  
            i = j ;  
        }  
      
        System.out.println(
             "Probabilité de changer de valeur : " + 
            ((proba [1][0] + proba [0][1])/(float)n)) ;  
        
        System.out.println(
             "Probabilité de rester collé : " + 
            ((proba [0][0] + proba [1][1])/(float)n)) ;  
    }  
}

Le résultat de l'exécution est le suivant.
 > Probabilité de changer de valeur : 0.182843
 > Probabilité de rester collé : 0.817157
Ce qui signifie que lorsque l'on a tiré un 0, alors on a 80% de chances de tirer à nouveau un 0... Si quelqu'un veut faire des paris, c'est le moment ! Si l'on déclare la variable Random comme il se doit :

Exemple 74. Utilisation correcte de Random

private  static Random random =  new Random() ;  
  
 public  static  int getRandomInt(int limite) {  
     return random.nextInt(limite) ;  
}

Alors le résultat devient :
 > Probabilité de changer de valeur : 0.498385
 > Probabilité de rester collé : 0.501615
Cette fois, lorsque l'on tire un 0, on a autant de chances de tirer ensuite un 0 ou un 1.
Java langage & API
Retour au blog Java le soir
Cours & Tutoriaux
Table des matières
Introduction : un peu d'histoire
1. Java : genèse d'un nouveau langage
Programmer en Java
1. Un premier exemple
1.1. Aperçu général, cycle de vie
1.2. Un premier programme
1.3. Programmes, applets, servlets, etc...
2. Une première classe
2.1. Écriture d'une classe
2.2. Instanciation de cette classe
3. Types de base, classes et objets
3.1. Types de base
3.2. Classes et objets
Classes Object et String
1. Introduction
2. La classe Object
2.1. La méthode toString()
2.2. La méthode clone()
2.3. La méthode equals()
2.4. La méthode hashCode()
2.5. La méthode finalize()
2.6. La méthode getClass()
3. La classe String
3.1. Introduction
3.2. Construction d'un objet de type String
3.3. Concaténation, StringBuffer et StringBuilder
3.4. Concaténations de chaînes de caractères depuis Java 8
3.5. Concaténations de chaînes de caractères depuis Java 11
3.6. Extraction d'une sous-chaîne de caractères
3.7. Comparaison de deux chaînes de caractères
3.8. Méthodes de comparaisons lexicographiques
3.9. Méthode de recherche de caractères
3.10. Méthode de modification de chaîne
3.11. Méthode de duplication
3.12. Support de l'unicode, internationalisation
Structure d'une classe
1. Introduction
2. Classes
2.1. Classes publiques
2.2. Classes internes
2.3. Classe membre
2.4. Classes locales
2.5. Classes anonymes
2.6. Le mot-clé this
3. Éléments statiques
3.1. Champ statique
3.2. Cas des constantes
3.3. Bloc statique
3.4. Classe membre statique
4. Membres d'une classe, visibilité
4.1. Bloc non statique
4.2. Accès à un membre, visibilité
4.3. Les champs
4.4. Signature d'une méthode
4.5. Les méthodes
4.6. Getters et Setters
5. Constructeur, instanciation
5.1. Chargement d'une classe
5.2. Constructeurs d'une classe
5.3. Instanciation d'un objet
5.4. Destruction d'objets
5.5. Le mot-clé final
6. Énumérations
6.1. Déclaration d'une énumération
6.2. Classe énumération
6.3. Méthode toString()
6.4. Méthode valueOf()
6.5. Méthode values()
6.6. Méthode ordinal()
6.7. Méthode compareTo()
6.8. Constructeurs privés
6.9. Classe utilitaire : EnumSet
Noms, opérateurs, tableaux
1. Introduction
2. Identificateurs, noms et expressions
2.1. Identificateurs et noms
2.2. Expressions
3. Opérateurs, ordre d'exécution
3.1. Ordre d'exécution
3.2. Les opérateurs
3.3. Les opérateurs ++ et --
3.4. Les opérateurs % et /
3.5. Les opérateurs <<, >> et >>>
3.6. L'opérateur instanceof
3.7. Les opérateurs &, | et ^
3.8. Les opérateurs && et ||
3.9. L'opérateur ? ... :
3.10. Les opérateurs d'affectation
4. Tableaux
4.1. Création d'un tableau
4.2. Initialisation d'un tableau
4.3. Utilisation d'un tableau comme un Object
4.4. Tableaux de tableaux
4.5. Copie de tableaux
5. Blocs, boucles et contrôles
5.1. Blocs
5.2. Mots-clés réservés
5.3. Tests : if et switch
5.4. Boucles : for, while, do ... while
5.5. Commandes continue et break
5.6. Commandes return et goto
Nombres, précision, calculs
1. Introduction
2. Calculs
2.1. Précision
2.2. Codage des nombres flottants
2.3. Le mot-clé strictfp
2.4. Conversion de types
3. Dépassements de capacité
3.1. Cas des entiers
3.2. Cas des flottants
3.3. Bibliothèques BigInteger et BigDecimal
4. Fonctions mathématiques
4.1. Fonctions usuelles
4.2. Générateurs aléatoires
5. Classes enveloppe
5.1. Associer les types de base à des objets
5.2. Auto-boxing
Héritage, abstraction, interfaces
1. Introduction
2. Abstraction et encapsulation
2.1. Abstraction
2.2. Encapsulation
3. Héritage
3.1. Définition de l'héritage
3.2. Conséquences pour les membres
3.3. Polymorphisme
3.4. Empêcher l'héritage
4. Classes abstraites
5. Interfaces
5.1. Introduction
5.2. Définition
5.3. Java 8 et les interfaces
5.4. Utilisation des interfaces
5.5. Définition de constantes dans les interfaces
5.6. Utilité des interfaces
Packages
1. Introduction
2. Notion de paquet
2.1. Déclaration d’appartenance à un paquet
2.2. Chargement d’une classe
2.3. Choix de nom
3. Archives, chemin de recherche, classpath
3.1. Archives
3.2. Variable CLASSPATH
3.3. Notion de classloader
3.4. Bilan sur les classes chargées
3.5. Visibilité
3.6. Conseils d'écriture, bibliothèque standard
Exceptions
1. Introduction
2. Erreurs et Exceptions
2.1. Classe Throwable, notion de stack trace
2.2. Classe Error
2.3. Classe RuntimException
2.4. Classe Exception
3. Déclenchement d'une exception
3.1. Exceptions déclenchées par la JVM
3.2. Exceptions déclenchées par l'application
4. Capter une exception
4.1. Traiter une exception localement
4.2. Code de captage
5. Créer ses propres exceptions
Entrées / sorties
1. Introduction
2. Notion de fichier
2.1. Introduction
2.2. La classe File
2.3. Construction d'une instance de File
2.4. Méthodes exposées
3. Flux de sortie
3.1. Introduction, notion de flux
3.2. Écriture de caractères, classe Writer
3.3. Bufferisation, construction d'un flux sur un autre
3.4. Utilisation de PrintWriter
3.5. Écriture d'octets, OuputStream
3.6. Écriture de types primitifs : DataOutputStream
3.7. Écriture d'objets : ObjectOutputStream
4. Flux d'entrée
4.1. Introduction
4.2. Lecture de caractères, classe Reader
4.3. Bufferisation, lecture ligne par ligne
4.4. Lecture d'octets : InputStream
4.5. Lecture de types primitifs : DataInputStream
4.6. Lecture d'objets : ObjectInputStream
5. Lecture et écriture de flux croisés
5.1. Introduction
5.2. Lire des caractères sur un flux binaire : classe InputStreamReader
5.3. Écrire des caractères sur un flux binaire : classe OutputStreamWriter
5.4. Remarque sur les jeux de caractères
6. Serialization d'objets
6.1. Enjeu de la sérialization d'objets
6.2. Serialization d'un objet
6.3. Sérialization d'une grappe d'objets
6.4. Première surcharge : méthode writeObject() readObject()
6.5. Deuxième surcharge : utilisation d'un externalizer
6.6. Troisième surcharge : utilisation d'un objet proxy
7. Flux compressés
7.1. Introduction
7.2. Flux de type gzip
7.3. Flux de type zip