[Tutoriel] L'aléatoire en C/C++
Posté le 25/02/2014 18:17
Besoin d'un peu d'aléatoire dans tes programmes C/C++ ? Ce tuto est fait pour toi !
Prérequis :
Tout d'abord, la génération de nombres aléatoires de manière "classique" en C/C++ repose sur les fonctions
rand() et
srand(). Celles-ci sont incluses dans la bibliothèque
stdlib.h, il sera donc nécessaire de l'inclure dans le header de notre projet.
Générer des nombres aléatoires :
Pour générer un nombre aléatoire, le microprocesseur est incapable d'en déterminer un lui même. Il faut l'aider un peu, et c'est ce que l'on a fait en rentrant dans la calculatrice une fonction de ce type :
static unsigned int lastrandom=0x12345678;
unsigned int random(int seed = 0)
{
if(seed) lastrandom = seed;
lastrandom = ( 0x41C64E6D * lastrandom ) + 0x3039;
return (lastrandom >> 16);
}
Bref, lors de son premier appel, la fonction
rand() retournera alors le nombre de seed 0, puis celui dont le seed est la dernière valeur générée. On aura donc, par exemple
-> 0x 52 07 EB F8
-> 0x 72 65 C8 62
-> Etc.
Le problème, c'est que du coup, à chaque lancement du programme on aura les mêmes valeurs, puisque le seed original est le même...
Pour palier à cela, on détermine plus ou moins aléatoirement le seed avant le premier appel de la fonction avec
srand(int position). On ne l'utilisera qu'une seule fois, au tout début du programme.
Le plus dur dans tout ça, c'est de trouver un nombre qui soit suffisamment "aléatoire" pour qu'il soit différent à chaque fois. Pour cela, nous allons utiliser le syscall
RTC_getTicks().
RTC_getTicks() est une fonction très pratique qui retourne le nombre de ticks, c'est à dire de fractions (1/128) de seconde qui se sont écoulés depuis une certaine date (inconnue). Autant dire que la probabilité que le programme soit lancé deux fois en 1/128 seconde est minimale.
Voici le code à inclure :
static int SysCallCode[] = {0xD201422B,0x60F20000,0x80010070};
static int (*SysCall)(int R4, int R5, int R6, int R7, int FNo ) = (void*)&SysCallCode;
int RTC_getTicks()
{
return (*SysCall)(0, 0, 0, 0, 0x3B);
}
Pour résumer, générer un nombre aléatoire se fait de cette façon :
1) On initialise avec
srand(RTC_getTicks());
2) On récupère le nombre avec
rand() :
int nbAlea = rand();
Traitement du nombre aléatoire :
C'est bien joli, mais
rand() ne retourne qu'un nombre compris entre 0 et 2^32-1 = 4294967295. Et nous voudrions un nombre entier entre 0 et 10, par exemple. C'est pour cela que nous allons modifier ce nombre :
Nombre entier entre 0 et max :
Nous allons utiliser le modulo (%), opération très pratique qui retourne le résultat de la division euclidienne de a par b (a%b). Or, puisque que ce reste n'est jamais supérieur à b, il est forcément égal à 0, 1, 2, 3, ..., b-1. Nous pouvons donc faire, pour générer un nombre entre 0 (inclus) et max (exclus) :
int rand_int(int max)
{
return rand() % max;
}
Nombre entier entre min et max :
Toujours en utilisant le modulo, il est possible de bidouiller la fonction ci-dessus pour inclure le fait d'avoir un minimum :
int rand_int_ab(int min, int max)
{
return rand() % (max - min) + min;
}
On a donc un nombre entier aléatoire entre min (inclus) et max (exclus).
Nombre décimal entre min et max :
Le problème du modulo, c'est qu'il ne permet que de travailler sur des entiers. Nous allons donc devoir nous en passer pour la génération de nombre décimaux (sous forme de float).
Nous avons vu que
rand() retournait un nombre entre 0 et 2^32. Donc en supposant que
RAND_MAX = 2^32,
rand() / RAND_MAX est compris entre 0 et 1. Sachant que
RAND_MAX est déjà défini dans "time.h", on peut l'utiliser tel quel. On en déduit alors la fonction suivante :
float rand_float_ab(float min, float max)
{
return ( rand() / RAND_MAX ) * (max - min) + min;
}
Conclusion et annexes :
Ce tuto touche à son terme, j'espère qu'il t'aura été utile, et que tu as compris les bases de la fonction
rand().
Si toutefois tu souhaite plus d'informations, ou que tu as une question, n'hésite-pas à laisser un commentaire ci-dessous !
De plus, tu trouvera peut-être ton bonheur sur l'ancien Site du Zéro, et plus particulièrement
ici.
A bientôt sur Planète Casio !
Citer : Posté le 05/12/2018 16:03 | #
Tu crées un fichier syscalls.s similaire à celui là : https://github.com/Zezombye/casiopy/blob/master/ports/minimal/syscalls.s
Ensuite il te suffit de l'ajouter à tes sources.
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 05/12/2018 16:57 | #
Merci bien mon ami
Ajouté le 05/12/2018 à 17:23 :
Bon j'ai regardé je suppose qu'à partir de cet exemple :
mov.l syscall_table, r2
mov.l 1f, r0
jmp @r2
nop
1:
.long 0x135
je dois faire :
mov.l syscall_table, r2
mov.l 1f, r0
jmp @r2
nop
1:
.long 0x135
mais à la place de 0x135 je mets quoi ? Je suppose que c'est le code du syscall
Franchement je comprends rien.
Citer : Posté le 05/12/2018 17:26 | #
Exact (bon c'est dans mon tuto, mais c'est une syntaxe un peu différente)
Oublie pas le .global au début.
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 05/12/2018 17:35 | #
Mais c'est quoi le code du syscall
Citer : Posté le 05/12/2018 17:36 | #
https://bible.planet-casio.com/simlo/chm/v20/fx_legacy_RTC.htm
0x03B
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 05/12/2018 18:10 | #
Ok, bon j'ai enfin réussi du coup j'ai fait un fichier syscalls.s où j'ai marqué :
_GetTicks:
mov.l syscall_table, r2
mov.l 1f, r0
jmp @r2
nop
1:
.long 0x03B
syscall_table:
.long 0x80010070
et dans le .c :
static unsigned int lastrandom=0x12345678;
unsigned int rand() {
int seed = GetTicks();
if(seed) lastrandom = seed;
lastrandom = ( 0x41C64E6D * lastrandom ) + 0x3039;
return (lastrandom >> 16);
}
Voilà je mets tout ça ici histoire que personne galère autant que moi. Merci Zezom, la fonction marche très bien de ce que j'ai vu
Et pour compiler le .s il faut faire comme ci que c'était un .c
Citer : Posté le 05/12/2018 22:29 | #
Tu as plusieurs choix, le canonique étant d'utiliser as. Mais gcc redirigera tout seul sur as en voyant que c'est de l'assembleur.
Citer : Posté le 06/12/2018 19:05 | #
du coup je reviens car ma fonction avait un aléatoire vraiment en mousse (obtenir 1000* la même valeur je pense pas avoir autant de chance)
du coup j'ai fait ça :
lastrandom += GetTicks();
lastrandom = ( 0x41C64E6D * lastrandom ) + 0x3039;
return (lastrandom >> 16);
}
ça a l'air de corriger le problème j'ai fait 10000 tours de boucle en comptant chaque cas de rand()%11 (0, 1, 2 ... 9, 10) et c'était à peu près égal. Par contre je sais pas si c'est le cas pour un modulo plus grand mais bon...
Pour en revenir au .s, j'ai juste implémenté le code dans le fichier .c :
" .global _GetTicks\n\t"
"\n\t"
"_GetTicks:\n\t"
" mov.l syscall_table, r2\n\t"
" mov.l 1f, r0\n\t"
" jmp @r2\n\t"
" nop\n\t"
"1:\n\t"
" .long 0x03B\n\t"
"\n\t"
"syscall_table:\n\t"
" .long 0x80010070\n\t"
"\n\t"
);
int GetTicks();
J'ai fait ça en piochant des infos sur internet donc j'étais un peu à l'aveugle si quelqu'un pourrait corriger ça (en tout cas ça marche)
EDIT: Serait-il mieux de faire un srand(GetTicks()) plutôt que de faire appel à cette fonction dans rand() ?
Citer : Posté le 06/12/2018 19:07 | #
Oups, mauvaise idée. Mets le code assembleur dans un fichier .s, vraiment.
Pour le générateur aléatoire, il est assez compliqué d'en faire des bons et très facile de tomber dans le panneau sur les critères. Mais selon ton application ce genre de fonctions peut te convenir...
Citer : Posté le 06/12/2018 19:10 | #
Ah mince, il y a une raison particulière ? Ça me permet d'éviter les fichiers en plus même si... c'est moche
Pendant que t'es là tu déconseilles toujours aussi vivement IsKeyDown() ?
Citer : Posté le 06/12/2018 19:46 | #
Oui, il y a des raisons... ça fait beaucoup de bruit pour le compilateur C, et à moins d'être très fort tu risques juste de casser le programme C. Honnêtement je trouve un fichier .s plus propre, et puis il faudra bien que tu touches à des projets avec plusieurs fichiers à un moment. (Si tu as du mal il y a un TDM à ce sujet.)
Et oui pour IsKeyDown(), toujours pour les mêmes raisons : vois ce topic.