Prima di tutto la presentazione che hai collegato parla solo di numeri casuali per motivi di sicurezza. Quindi non pretende che Random
sia dannoso per scopi non di sicurezza.
Ma sostengo che lo sia. L'implementazione .net 4 di Random
è difettosa in diversi modi. Consiglio di usarlo solo se non ti interessa la qualità dei tuoi numeri casuali. Consiglio di utilizzare migliori implementazioni di terze parti.
Difetto 1: la semina
Il costruttore predefinito esegue il seed con l'ora corrente. Pertanto, tutte le istanze di Random
create con il costruttore predefinito in un breve lasso di tempo (circa 10 ms) restituiscono la stessa sequenza. Questo è documentato e "by-design". Ciò è particolarmente fastidioso se si desidera eseguire il multi-thread del codice, poiché non è possibile creare semplicemente un'istanza di Random
all'inizio dell'esecuzione di ogni thread.
La soluzione alternativa consiste nel prestare la massima attenzione quando si utilizza il costruttore predefinito e eseguire manualmente il seed quando necessario.
Un altro problema qui è che lo spazio seed è piuttosto piccolo (31 bit). Quindi, se generi 50k istanze di Random
con semi perfettamente casuali, probabilmente otterrai due volte una sequenza di numeri casuali (a causa del paradosso del compleanno ). Quindi anche la semina manuale non è facile da ottenere.
Difetto 2: la distribuzione dei numeri casuali restituiti da Next(int maxValue)
è parziale
Ci sono parametri per i quali Next(int maxValue)
chiaramente non è uniforme. Ad esempio se calcoli r.Next(1431655765) % 2
otterrai 0
circa 2/3 dei campioni. (Codice di esempio alla fine della risposta.)
Difetto 3: il NextBytes()
metodo è inefficiente.
Il costo per byte di NextBytes()
è grande circa quanto il costo per generare un campione intero completo con Next()
. Da questo sospetto che creino effettivamente un campione per byte.
Una migliore implementazione utilizzando 3 byte di ogni campione accelererebbe NextBytes()
di quasi un fattore 3.
Grazie a questo difetto Random.NextBytes()
è solo circa il 25% più veloce rispetto System.Security.Cryptography.RNGCryptoServiceProvider.GetBytes
alla mia macchina (Win7, Core i3 2600MHz).
Sono sicuro che se qualcuno ispezionasse il codice sorgente / byte decompilato troverebbe ancora più difetti di quelli che ho trovato con la mia analisi della scatola nera.
Esempi di codice
r.Next(0x55555555) % 2
è fortemente prevenuto:
Random r = new Random();
const int mod = 2;
int[] hist = new int[mod];
for(int i = 0; i < 10000000; i++)
{
int num = r.Next(0x55555555);
int num2 = num % 2;
hist[num2]++;
}
for(int i=0;i<mod;i++)
Console.WriteLine(hist[i]);
Prestazione:
byte[] bytes=new byte[8*1024];
var cr=new System.Security.Cryptography.RNGCryptoServiceProvider();
Random r=new Random();
for(int i=0;i<100000;i++)
{
r.NextBytes(bytes);
}
for(int i=0;i<100000;i++)
{
for(int j=0;j<bytes.Length;j++)
bytes[j]=(byte)r.Next();
}
for(int i=0;i<100000;i++)
{
for(int j=0;j+2<bytes.Length;j+=3)
{
int num=r.Next();
bytes[j+2]=(byte)(num>>16);
bytes[j+1]=(byte)(num>>8);
bytes[j]=(byte)num;
}
}
for(int i=0;i<100000;i++)
{
cr.GetBytes(bytes);
}