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) ;
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) ; }
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)) ; } }
> Probabilité de changer de valeur : 0.182843 > Probabilité de rester collé : 0.817157Ce 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) ; }
> Probabilité de changer de valeur : 0.498385 > Probabilité de rester collé : 0.501615Cette fois, lorsque l'on tire un 0, on a autant de chances de tirer ensuite un 0 ou un 1.
String
StringBuffer
et
StringBuilder