J'implémentais une table de hachage en C dans le cadre d'un projet sur lequel je travaille et j'utilise des insertions aléatoires pour le tester lorsque j'ai remarqué que rand()
sous Linux, il semble répéter les nombres beaucoup plus souvent que sur Mac. RAND_MAX
est 2147483647 / 0x7FFFFFFF sur les deux plates-formes. Je l'ai réduit à ce programme de test qui fait un tableau d'octets de RAND_MAX+1
long, génère RAND_MAX
des nombres aléatoires, note si chacun est un doublon et le vérifie dans la liste comme on le voit.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
int main() {
size_t size = ((size_t)RAND_MAX) + 1;
char *randoms = calloc(size, sizeof(char));
int dups = 0;
srand(time(0));
for (int i = 0; i < RAND_MAX; i++) {
int r = rand();
if (randoms[r]) {
// printf("duplicate at %d\n", r);
dups++;
}
randoms[r] = 1;
}
printf("duplicates: %d\n", dups);
}
Linux génère régulièrement environ 790 millions de doublons. Mac n'en génère systématiquement qu'un, de sorte qu'il parcourt chaque nombre aléatoire qu'il peut générer presque sans répétition. Quelqu'un peut-il m'expliquer comment cela fonctionne? Je ne peux rien dire de différent des pages de manuel, je ne peux pas dire quel RNG chacun utilise et je ne trouve rien en ligne. Merci!
Réponses:
Alors qu'au début, il peut sembler que macOS
rand()
est en quelque sorte meilleur pour ne pas répéter de chiffres, il convient de noter qu'avec cette quantité de chiffres générés, il devrait voir beaucoup de doublons (en fait, environ 790 millions, ou (2 31 -1 ) / e ). De même, l'itération à travers les nombres dans l'ordre ne produirait pas non plus de doublons, mais ne serait pas considérée comme très aléatoire. Ainsi, l'rand()
implémentation Linux est dans ce test indiscernable d'une véritable source aléatoire, contrairement à macOSrand()
.Une autre chose qui semble surprenante à première vue est de savoir comment le macOS
rand()
peut si bien éviter les doublons. En regardant son code source , nous constatons que l'implémentation est la suivante:Cela se traduit en effet par tous les nombres compris entre 1 et
RAND_MAX
, inclus, exactement une fois, avant que la séquence ne se répète à nouveau. Étant donné que l'état suivant est basé sur la multiplication, l'état ne peut jamais être nul (ou tous les états futurs seraient également nuls). Ainsi, le nombre répété que vous voyez est le premier, et zéro est celui qui n'est jamais renvoyé.Apple promeut l'utilisation de meilleurs générateurs de nombres aléatoires dans sa documentation et ses exemples depuis au moins aussi longtemps que macOS (ou OS X) a existé, de sorte que la qualité de
rand()
n'est probablement pas jugée importante, et ils sont juste restés avec l'un des les générateurs pseudo-aléatoires les plus simples disponibles. (Comme vous l'avez noté, leurrand()
est même commenté avec une recommandation d'utilisation à laarc4random()
place.)Sur une note connexe, le générateur de nombres pseudo-aléatoires le plus simple que j'ai pu trouver et qui produit des résultats décents dans ce (et beaucoup d'autres) tests d'aléatoire est xorshift * :
Cette implémentation génère presque exactement 790 millions de doublons dans votre test.
la source
arc4random()
même code que derrièrerand()
et obtenir un bonrand()
résultat. Plutôt que d'essayer d'orienter les programmeurs vers un code différent, créez simplement de meilleures fonctions de bibliothèque. "ils viennent de rester" est leur choix.rand()
rend si mauvais qu'il n'est pas utile pour une utilisation pratique: pourquoi rand ()% 7 renvoie toujours 0? , Rand ()% 14 génère uniquement les valeurs 6 ou 13rand
, que le relancer avec la même graine produise la même séquence. OpenBSDrand
est cassé et n'obéit pas à ce contrat.rand()
avec la même graine, produise la même séquence entre différentes versions de la bibliothèque? Une telle garantie pourrait être utile pour les tests de régression entre les versions de bibliothèque, mais je ne trouve aucune exigence en C pour cela.MacOS fournit une fonction rand () non documentée dans stdlib. Si vous ne le laissez pas, les premières valeurs qu'il génère sont 16807, 282475249, 1622650073, 984943658 et 1144108930. Une recherche rapide montrera que cette séquence correspond à un générateur de nombres aléatoires LCG très basique qui réitère la formule suivante:
Étant donné que l'état de ce RNG est entièrement décrit par la valeur d'un seul entier 32 bits, sa période n'est pas très longue. Pour être précis, il se répète toutes les 2 31 - 2 itérations, produisant chaque valeur de 1 à 2 31 - 2.
Je ne pense pas qu'il existe une implémentation standard de rand () pour toutes les versions de Linux, mais il existe une fonction glibc rand () qui est souvent utilisée. Au lieu d'une seule variable d'état 32 bits, celle-ci utilise un pool de plus de 1000 bits, qui à toutes fins utiles ne produira jamais une séquence entièrement répétitive. Encore une fois, vous pouvez probablement découvrir quelle version vous avez en imprimant les premières sorties de ce RNG sans l'amorcer d'abord. (La fonction glibc rand () produit les nombres 1804289383, 846930886, 1681692777, 1714636915 et 1957747793.)
Donc, la raison pour laquelle vous obtenez plus de collisions sous Linux (et presque pas sous MacOS) est que la version Linux de rand () est fondamentalement plus aléatoire.
la source
rand()
doit se comporter comme un avecsrand(1);
rand()
dans macOS est disponible: opensource.apple.com/source/Libc/Libc-1353.11.2/stdlib/FreeBSD/… FWIW, j'ai exécuté le même test contre ce compilé à partir de la source et il en résulte effectivement un seul doublon. Apple a encouragé l'utilisation d'autres générateurs de nombres aléatoires (commearc4random()
avant la prise de contrôle de Swift) dans leurs exemples et leur documentation, de sorte que l'utilisation derand()
n'est probablement pas très courante dans les applications natives sur leurs plates-formes, ce qui peut expliquer pourquoi ce n'est pas mieux.rand()
n'était pas documenté, mais @Arkku a fourni un lien vers la source apparente. L'un de vous sait-il pourquoi je ne trouve pas ce fichier sur mon système et pourquoi je ne le vois queint rand(void) __swift_unavailable("Use arc4random instead.");
sur Macstdlib.h
? Je suppose que le code lié à @Arkku est juste compilé dans ... quelle bibliothèque?/usr/lib/libc.dylib
. =)rand()
un programme C donné des utilisations n'est pas déterminé par le « compilateur » ou le « système d' exploitation », mais plutôt la mise en œuvre de la bibliothèque standard C (par exempleglibc
,libc.dylib
,msvcrt*.dll
).rand()
est défini par la norme C, et la norme C ne spécifie pas quel algorithme utiliser. De toute évidence, Apple utilise un algorithme inférieur à votre implémentation GNU / Linux: celui de Linux est indiscernable d'une véritable source aléatoire dans votre test, tandis que l'implémentation Apple mélange simplement les chiffres.Si vous voulez des nombres aléatoires de n'importe quelle qualité, utilisez un meilleur PRNG qui donne au moins quelques garanties sur la qualité des nombres qu'il retourne, ou lisez simplement
/dev/urandom
ou similaire. Le dernier vous donne des numéros de qualité cryptographique, mais est lent. Même s'il est trop lent par lui-même, il/dev/urandom
peut fournir d'excellentes graines à un autre PRNG plus rapide.la source
En général, la paire rand / srand a été considérée comme obsolète depuis longtemps en raison des bits de poids faible affichant moins de caractère aléatoire que les bits de poids fort dans les résultats. Cela peut ou non avoir quelque chose à voir avec vos résultats, mais je pense que c'est toujours une bonne occasion de se rappeler que même si certaines implémentations rand / srand sont maintenant plus à jour, les implémentations plus anciennes persistent et il est préférable d'utiliser aléatoire (3 ). Sur ma boîte Arch Linux, la note suivante est toujours dans la page de manuel de rand (3):
Juste en dessous, la page de manuel donne en fait des exemples d'implémentation très courts et très simples de rand et srand qui concernent les RNG LC les plus simples que vous ayez jamais vus et ayant un petit RAND_MAX. Je ne pense pas qu'ils correspondent à ce qui se trouve dans la bibliothèque standard C, si c'est le cas. Ou du moins j'espère que non.
En général, si vous allez utiliser quelque chose de la bibliothèque standard, utilisez random si vous le pouvez (la page de manuel le répertorie comme standard POSIX vers POSIX.1-2001, mais rand est un moyen standard avant que C ne soit même standardisé) . Ou mieux encore, ouvrez les recettes numériques (ou recherchez-les en ligne) ou Knuth et implémentez-en une. Ils sont vraiment faciles et vous n'avez vraiment besoin de le faire qu'une seule fois pour avoir un RNG à usage général avec les attributs dont vous avez le plus souvent besoin et qui est de qualité connue.
la source
rand()
`` mieux '' signifierait le ralentir (ce qui serait probablement le cas - les nombres aléatoires sécurisés cryptographiquement demandent beaucoup d'efforts), alors il est probablement préférable de le garder rapide même s'il est légèrement plus prévisible. Exemple: nous avions une application de production qui prenait beaucoup de temps à démarrer, que nous avons retracée à un RNG dont l'initialisation devait attendre la génération d'une entropie suffisante ... Il s'est avéré qu'elle n'avait pas besoin d'être aussi sécurisée, donc la remplacer par un «pire» RNG était une grande amélioration.