How to generate a cryptographically secure Double between 0 and 1?
Asked Answered
G

3

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)?

Gelb answered 18/5, 2010 at 3:45 Comment(2)
What distribution do you want? Uniform?Rhabdomancy
Yes, uniform distribution. Will update now.Gelb
D
33

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.

Dipper answered 18/5, 2010 at 4:50 Comment(4)
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().Gelb
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?Gelb
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).Pour
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.Dipper
S
-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);
Shirlyshiroma answered 18/5, 2010 at 3:49 Comment(3)
Might want to add a Math.Abs( l ) to ensure the resulting double is positive.Sinuosity
This has a very different distribution compared to NextDouble. It will generate numbers very close to zero almost all the time.Fredericafrederich
Thx for the info on NaN. Didn't know that it was a specific byte sequence.Adila
E
-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

Estivate answered 19/2, 2022 at 0:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.