Random in PHP
Een computer is logisch, hoe kan zo’n logisch apparaat nou random getallen maken? Vandaag nemen we een kijk in de PHP source om te kijken hoe het in PHP gedaan wordt.
De PHP source
De PHP source is geschreven in C en je kunt een hele makkelijk navigeerbare weergave van de source op http://lxr.php.net/ vinden. Hier kun je zoeken naar functies en makkelijk naar de source van functies navigeren.
Er is 1 basis conceptje die ik moet vertellen, de rest zal je gaanderweg wel gaan begrijpen. Dit concept is defines in C. De C taal wordt eerst gecompiled en vervolgens wordt die compiled code omgezet in uitvoerbare code. Tijdens het compilen kun je wat dingen doe die PHP niet kan. Bijv. het gebruik van defines. Defines zijn woorden die worden omgezet in de waarde die je er aan meegeeft. Laten we beginnen met een simpele constante:
1
#define M_PI 3.14159265358979323846
Wanneer we nu in onze code M_PI
gebruiken wordt dit vervangen door dit
getal:
1
long omtrek = M_PI * 4;
Hier staat na het compilen eigenlijk:
1
long omtrek = 3.14159265358979323846 * 4;
Naast het invullen van getallen of strings kun je ook macro’s definiëren. Bijvoorbeeld deze macro:
1
#define INCREMENT(x) x++
Nu zal INCREMENT(5)
worden omgezet in 6
.
Random getallen
De andere basis kennis is het doorhebben hoe je random getallen maakt. Random getallen zijn eigenlijk een sequence, je begint met een getal en stopt die in een formule. Uit deze formule komt een getal, dit getal is het random getal en die wordt opgeslagen in een variabele (dezelfde variabele als het begin getal). Dit getal wordt dan weer gebruikt om met dezelfde functie weer een getal te genereren, die ook weer wordt opgeslagen en als je de functie nog een keer aanroept komt er weer een ander getal uit.
Je zal wel begrijpen dat je dan een hele rij van allemaal nieuwe random getallen krijgt. Wanneer je met hetzelfde getal begint zal er altijd dezelfde rij uitkomen. Dit eerste getal noemen we een seed. De seed wordt random gecreeërd, bijvoorbeeld door de tijd en datum te gebruiken. Op deze manier heb je dus nooit dezelfde random reeks getallen.
De rand
functie
Nu je de basiskennis hebt gaan we kijken in de PHP source code. De meeste
functies worden aangemaakt door PHP_FUNCTION(rand)
. Als we opzoek willen
naar de rand
functie zoeken we op "PHP_FUNCTION rand"
(vergeet de quotes
niet). In de resultaten krijgen we dan 2 bestanden: php_math.h
en
rand.c
. De *.h
bestanden zijn header bestanden, die vertellen wat er
allemaal in de *.c
bestanden staat. De *.c
bestanden bevatten de echte
code. We klikken dus op die link en navigeren naar regel 290:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PHP_FUNCTION(rand)
{
long min;
long max;
long number;
int argc = ZEND_NUM_ARGS();
if (argc != 0 && zend_parse_parameters(argc TSRMLS_CC, "ll", &min, &max) == FAILURE)
return;
number = php_rand(TSRMLS_C);
if (argc == 2) {
RAND_RANGE(number, min, max, PHP_RAND_MAX);
}
RETURN_LONG(number);
}
En dat is de functie!
Parsen van argumenten
Als eerst zien we dat er 4 variabelen gedeclareerd worden:
1
2
3
4
long min;
long max;
long number;
int argc = ZEND_NUM_ARGS();
De eerste 3 variabelen zijn of argumenten of variabele die we verder in de
functie gaan gebruiken. De 4e variabele, argc
, is het aantal argumenten.
Zend is de engine achter PHP en we kunnen wel raden wat ZEND_NUM_ARGS()
doet, we gaan dus niet naar die source code kijken.
Dan krijgen we een check voor de argumenten:
1
2
if (argc != 0 && zend_parse_parameters(argc TSRMLS_CC, "ll", &min, &max) == FAILURE)
return;
De check argc != 0
betekend dat we pas argumenten gaan checken als er
argumenten zijn, dit komt omdat de rand
functie ook zonder argumenten
aangeroepen kan worden. Zodra er wel argumenten zijn moeten er meteen 2 zijn.
Met zend_parse_parameters
parsen we de argumenten. We zien "ll"
, dit
betekend dat beide argumenten long
(een soort float) zijn. Daarachter zien
we &min
en &max
, dit betekend dat de waarde van de argumenten worden mee
gegeven aan de variabelen min
en max
(die we hiervoor al gedeclareerd
hadden).
Het maken van de seed
Dan gaan we de seed maken:
1
number = php_rand(TSRMLS_C);
In deze regel roepen we de functie php_rand
(een interne functie die niet in
PHP bestaat) aan. Die geven we ook weer een waarde mee, die is voor nu even
niet interessant.
Wat wel interessant is hoe PHP die random seed maakt. Dus klikken we op de
functie php_rand
om die source te bekijken:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PHPAPI long php_rand(TSRMLS_D)
{
long ret;
if (!BG(rand_is_seeded)) {
php_srand(GENERATE_SEED() TSRMLS_CC);
}
#ifdef ZTS
ret = php_rand_r(&BG(rand_seed));
#else
# if defined(HAVE_RANDOM)
ret = random();
# elif defined(HAVE_LRAND48)
ret = lrand48();
# else
ret = rand();
# endif
#endif
return ret;
}
Wat we hier heel in het kort zien is dat de functie, afhankelijk van bepaalde
instellingen, de C functies rand
, lrand48
of random
gebruikt.
Het maken van het random getal
1
2
3
if (argc == 2) {
RAND_RANGE(number, min, max, PHP_RAND_MAX);
}
Zodra er 2 argumenten zijn gegeven wordt de RAND_RANGE
define functie
gebruikt om het random nummer tussen 2 waardes te maken. Zodra je dus geen
argumenten geeft, krijg je gewoon de seed terug.
Nu zijn we natuurlijk benieuwd wat voor algoritme PHP gebruikt voor het maken
van het random getal, dus klikken we erop en gaan we naar een
php_rand.h
bestand:
1
2
#define RAND_RANGE(__n, __min, __max, __tmax) \
(__n) = (__min) + (long) ((double) ( (double) (__max) - (__min) + 1.0) * ((__n) / ((__tmax) + 1.0)))
En dat is de magische formule achter PHP’s rand
functie! Er wordt mooi
gespeelt met wat afronden (de (long)
’s en (double)
’s in de code), de min
en max (__min
en __max
) worden een paar keer gebruikt en ook onze seed
(__n
) wordt gebruikt. Vervolgens zien we ook (__n) = ...
, waardoor onze
seed dus weer vernieuwd wordt door het gegenereerde random getal.
Het returnen van het getal
Het enige dat ons nog rest is het returnen van de gemaakt long
:
1
RETURN_LONG(number);
Achter deze simpele regel zit weer heel wat zend gedoe, maar daar bemoeien we ons vandaag niet mee.
Dus…
Je hoofd duizeld nu van de C code, maar je hebt wel begrepen hoe je kan kijken hoe PHP functies werken. Ook heb je geleerd dat een goede random functie 4 factoren heeft: Niet voorspelbaar; geen herhalend patroon; alle getallen moeten ongeveer gelijk voorkomen; dezelfde seed zorgt voor dezelfde rij random getallen.