Accueil > Technique > Informatique > PHP > mt_rand ou les tribulations de php au pays des grands nombres (...)
Robert Sebille
mt_rand ou les tribulations de php au pays des grands nombres aléatoires.
lundi 17 mars 2014, par
Le contexte
J’avais besoin d’une application compliante smartphone et pouvant produire de très grands nombres entiers aléatoires (jusqu’à la somme de 15 * 1E15 - 1 = 15E15 - 15). Elle est ici : Orientée web et le générateur de nombres étant coté serveur, qui sont presque toujours maintenant 64-bits, pas de soucis. Puis j’ai voulu être indépendant d’internet sur mon smartphone (android). J’ai installé dessus l’application et un serveur web / php. Mais android est actuellement 32-bits, et donc, le générateur de nombre aléatoire de php, mt_rand, n’acceptant que des entiers, de maximum 2 147 483 647 (2^31), ça dysfontionnait. La solution était donc de n’utiliser que des nombres inférieurs à 2^31 sur les systèmes (CPU ou OS) 32 bits. Mais ...
Les tribulations
Exit mt_getrandmax()
php possède une fonction mt_getrandmax() qui retourne la plus grande valeur aléatoire possible que peut retourner la fonction mt_rand(). Sauvé, pensez-vous ? Et bien non, car mt_getrandmax() retourne 2 147 483 647 aussi bien sur les systèmes 32-bits que 64-bits. Exit mt_getrandmax().
Lever une exception
Je décide alors d’écrire une fonction qui teste si un nombre est trop grand pour mt_rand et qui lève une exception si c’est le cas ; je suis sur un 64-bits, et je vais donc tester un nombre trop grand pour un 64-bits (+ que 9 223 372 036 854 775 808) ; voici la fonction et son test :
<?php
// fichier ajax/check_randmax.php, pour l'exemple plus loin.
$mtrandmax = 9999999999999999*999;
function mtr($mtrm) {
if (!mt_rand(0, $mtrm)) {
throw new Exception('32 bits.');
return null;
}
else {
return mt_rand(0, $mtrm);
}
}
$sys32bits="0";
try {
$test = mtr($mtrandmax);
} catch (Exception $e) {
$sys32bits="1";
}
echo $sys32bits;
?>
C’est (presque) ok sur le 64-bits
Je teste, c’est ok. Sauf que si le max de mt_rand est trop grand, il deviendra une valeur inférieure au min, et mt_rand émet une alerte de niveau E_WARNING et retourne FALSE si le paramètre max < au paramètre min. Le test est via ajax, ce qui n’empêche pas le warning d’être envoyé dans le retour de la requête et ce qui rend un test difficile sur ce retour. Bon là, il suffira de tester après retour de la requête que le système n’est pas 32-bits ($sys32bits="0" ;) et c’est bon. exemple avec jQuery :
$.get("ajax/check_randmax.php",function(result) {
if (result != "0") { // pas result == "1", car on risque de se faire renvoyer l'éventuel warning
// 64-bits
} else {
// 32-bits
}
});
C’est bon sur mon 64-bits, je décide de n’utiliser que des nombres inférieurs à 1E9 - 1 pour les 32-bits, et je fixe donc le $mtrandmax du test à 1E10 - 1$mtrandmax = 9999999999;
Je charge sur mon smartphone, je teste et ...
Arrrgh ! l’exception n’est pas levée sur le 32-bit !
Je revérifie les nombres, reteste sur le 64-bits, ça marche toujours, mais rien à faire sur le smartphone 32-bits. Je suis perplexe. Quand on ne sait plus quoi essayer, alors autant essayer n’importe quoi même si ça n’a pas de sens. je fixe $mtrandmax au maximum (2 147 483 647) + 1$mtrandmax = 2147483648;
Retour sur le smartphone, et ça marche ! Craignant toujours un possible effet de bord, je n’aime pas rester trop près des limites, et je fixe :$mtrandmax = 3333333333;
Test sur le smartphone : ça marche toujours !
Par acquit de conscience, je reteste $mtrandmax = 9999999999;
et de nouveau, aucune exception n’est levée. Pourquoi avec ce nombre là (9999999999) précis ? Je n’en sais toujours rien !
Je suis donc resté avec $mtrandmax = 3333333333;