Come ottenere valori doppi casuali da valori casuali di array di byte?
Domanda
Vorrei usare RNGCryptoServiceProvider come fonte di numeri casuali. Dato che può solo emetterli come una matrice di valori byte, come posso convertirli in un valore doppio da 0 a 1 preservando l'uniformità dei risultati?
Soluzione
byte[] result = new byte[8];
rng.GetBytes(result);
return (double)BitConverter.ToUInt64(result,0) / ulong.MaxValue;
Altri suggerimenti
Ecco come lo farei.
private static readonly System.Security.Cryptography.RNGCryptoServiceProvider _secureRng;
public static double NextSecureDouble()
{
var bytes = new byte[8];
_secureRng.GetBytes(bytes);
var v = BitConverter.ToUInt64(bytes, 0);
// We only use the 53-bits of integer precision available in a IEEE 754 64-bit double.
// The result is a fraction,
// r = (0, 9007199254740991) / 9007199254740992 where 0 <= r && r < 1.
v &= ((1UL << 53) - 1);
var r = (double)v / (double)(1UL << 53);
return r;
}
Per coincidenza
9007199254740991/9007199254740992 è ~ = 0.99999999999999988897769753748436
che è ciò che il metodoRandom.NextDouble
restituirà come valore massimo (vedere https://msdn.microsoft.com/en-us/library/system .random.nextdouble (v = vs.110) aspx ).
In generale, la deviazione standard di una distribuzione uniforme continua è (max - min) / sqrt (12).
Con una dimensione del campione di 1000 ottengo in modo affidabile entro un margine di errore del 2%.
Con una dimensione del campione di 10000 sto ottenendo in modo affidabile un margine di errore dell'1%.
Ecco come ho verificato questi risultati.
[Test]
public void Randomness_SecureDoubleTest()
{
RunTrials(1000, 0.02);
RunTrials(10000, 0.01);
}
private static void RunTrials(int sampleSize, double errorMargin)
{
var q = new Queue<double>();
while (q.Count < sampleSize)
{
q.Enqueue(Randomness.NextSecureDouble());
}
for (int k = 0; k < 1000; k++)
{
// rotate
q.Dequeue();
q.Enqueue(Randomness.NextSecureDouble());
var avg = q.Average();
// Dividing by n−1 gives a better estimate of the population standard
// deviation for the larger parent population than dividing by n,
// which gives a result which is correct for the sample only.
var actual = Math.Sqrt(q.Sum(x => (x - avg) * (x - avg)) / (q.Count - 1));
// see http://stats.stackexchange.com/a/1014/4576
var expected = (q.Max() - q.Min()) / Math.Sqrt(12);
Assert.AreEqual(expected, actual, errorMargin);
}
}
È possibile utilizzare il metodo BitConverter.ToDouble (...). Prende in una matrice di byte e restituirà un doppio. Esistono metodi corrispondenti per la maggior parte degli altri tipi primitivi, nonché un metodo per passare dalle primitive a una matrice di byte.
Usa BitConverter per convertire una sequenza di byte casuali in un doppio:
byte[] random_bytes = new byte[8]; // BitConverter will expect an 8-byte array
new RNGCryptoServiceProvider().GetBytes(random_bytes);
double my_random_double = BitConverter.ToDouble(random_bytes, 0);