15

I know how to generate a random number between 0 and 1 using the NextDouble method of the pseudo-random number generator.

var rng1 = new System.Random();
var random1 = rng1.NextDouble(); // generates a random double between 0 and 1.0

And I know how to fill a random byte array using the cryptographically secure random number generator.

Byte[] bytes = new Byte[8];
var rng2 = new System.Security.Cryptography.RNGCryptoServiceProvider();
rng2.GetBytes(bytes); // generates 8 random bytes

But how can I convert the byte-array output of RNGCryptoServiceProvider into a random number uniformly distributed between 0 (inclusive) and 1 (exclusive)?

Portman
  • 31,785
  • 25
  • 82
  • 101

3 Answers3

32

It appears to me that the solutions so far will have uneven distribution due to taking the inverse. For an even distribution I'd think you want something like this.

// Step 1: fill an array with 8 random bytes
var rng = new RNGCryptoServiceProvider();
var bytes = new Byte[8];
rng.GetBytes(bytes);
// Step 2: bit-shift 11 and 53 based on double's mantissa bits
var ul = BitConverter.ToUInt64(bytes, 0) / (1 << 11);
Double d = ul / (Double)(1UL << 53);

Note that you can't just divide the UInt64 into UInt64.MaxValue, because a double doesn't have enough bits, and there's no way to get unique outputs for all your inputs. So you can/must throw some bits away.

Portman
  • 31,785
  • 25
  • 82
  • 101
Conrad Albrecht
  • 1,976
  • 2
  • 15
  • 19
  • This is great, thank you. Just added the required second param to BitConverter.ToUInt64 and a missing parent on line 2. Testing now to ensure it's an equivalent distribution to Random.NextDouble(). – Portman May 18 '10 at 05:05
  • 1
    Edit #3 is working nicely: after 1 million iterations, minimum of .0000001, maximum of .999999, average of .5000003 after 1 million iterations. Mind if I clean up the edit history? – Portman May 18 '10 at 05:34
  • Why not just grab 53 bits instead of doing weird things with the second-to-last line? (Seriously, it took me a while to understand what exactly you did there). – Joey May 18 '10 at 14:29
  • 1
    1. I was in a hurry, that was the first coding that I thought of. 2. Feel free to show us your cleaner code to "grab 53 bits". 3. I actually realized later that "BitConverter.ToUInt64(bytes, 0) >> 11" would be cleaner but I was out of time. – Conrad Albrecht May 18 '10 at 15:47
-1

Well, I would not call a 64-bit random number "cryptographically secure" - you'd want a lot more bits than that to be "cryptographically secure". But anyway, you could do something like this:

var bytes = // assume this contains 8 bytes of random numbers

long l = BitConverter.ToInt64(bytes);
double d = Math.Abs(1 / (double)l);
Dean Harding
  • 71,468
  • 13
  • 145
  • 180
-1

Since RNGCryptoServiceProvider is obsolete in .NET 6

https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.rngcryptoserviceprovider?view=net-6.0

if you want "real" NextDouble you can use RandomNumberGenerator like this

How to get NextDouble from cryptogaphy random RandomNumberGenerator

AnGG
  • 679
  • 3
  • 9