I'm using the RNG crypto provider to generate numbers in a range the truly naive way:
byte[] bytes = new byte[4]; int result = 0; while(result < min || result > max) { RNG.GetBytes(bytes); result = BitConverter.ToInt32(bytes); }
This is great when the range is wide enough such that there is a decent chance of getting a result, but earlier today I hit a scenario where the range is sufficiently small (within 10,000 numbers) that it can take an age.
So I've been trying to think of a better way that will achieve a decent distribution but will be faster. But now I'm getting into deeper maths and statistics that I simply didn't do at school, or at least if I did I have forgotten it all!
My idea is:
Like I say, this could be exceedingly naive, but I am sure it will return a match in a narrow range faster than the current implementation. I'm not in front of a computer at the moment so can't test, will be doing that tomorrow morning UK time.
But of course speed isn't my only concern, otherwise I would just use Random (needs a couple of tick marks there to format correctly if someone would be kind enough - they're not on the Android keyboard!).
The biggest concern I have with the above approach is that I am always throwing away up to 7 bits that were generated by the prng, which seems bad. I thought of ways to factor them in (e.g. a simple addition) but they seem terribly unscientific hacks!
I know about the mod trick, where you only have to generate one sequence, but I also know about its weaknesses.
Is this a dead end? Ultimately if the best solution is going to be to stick with the current implementation I will, I just feel that there must be a better way!
Stephen Toub and Shawn Farkas has co-written an excellent article on MSDN called Tales From The CryptoRandom that you should definitely read if you're experimenting with RNGCryptoServiceProviders
In it they provide an implementation that inherits from System.Random (which contains the nice range-random method that you're looking for) but instead of using pseudo random numbers their implementation uses the RNGCryptoServiceProvider.
The way he has implemented the Next(min, max) method is as follows:
public override Int32 Next(Int32 minValue, Int32 maxValue) { if (minValue > maxValue) throw new ArgumentOutOfRangeException("minValue"); if (minValue == maxValue) return minValue; Int64 diff = maxValue - minValue; while (true) { _rng.GetBytes(_uint32Buffer); UInt32 rand = BitConverter.ToUInt32(_uint32Buffer, 0); Int64 max = (1 + (Int64)UInt32.MaxValue); Int64 remainder = max % diff; if (rand < max - remainder) { return (Int32)(minValue + (rand % diff)); } } }
The reasoning for the choice of implementation as well as a detailed analysis about loss of randomness and what steps they are taking to produce high-quality random numbers is in their article.
Thread safe bufferred CryptoRandom
I've written an extended implementation of Stephen's class which utilized a random buffer in order to minimize any overhead of calling out to GetBytes(). My implementation also uses synchronization to provide thread safety, making it possible to share the instance between all your threads to make full use of the buffer.
I wrote this for a very specific scenario so you should of course profile whether or not is makes sense for you given the specific contention and concurrency attributes of your application. I threw the code up on github if you wan't to check it out.
Threadsafe buffered CryptoRandom based on Stephen Toub and Shawn Farkas' implementation
When I wrote it (a couple of years back) I seem to have done some profiling as well
Results produced by calling Next() 1 000 000 times on my machine (dual core 3Ghz) System.Random completed in 20.4993 ms (avg 0 ms) (first: 0.3454 ms) CryptoRandom with pool completed in 132.2408 ms (avg 0.0001 ms) (first: 0.025 ms) CryptoRandom without pool completed in 2 sec 587.708 ms (avg 0.0025 ms) (first: 1.4142 ms) |---------------------|------------------------------------| | Implementation | Slowdown compared to System.Random | |---------------------|------------------------------------| | System.Random | 0 | | CryptoRand w pool | 6,6x | | CryptoRand w/o pool | 19,5x | |---------------------|------------------------------------|
Please note that theese measurements only profile a very specific non-real-world scenario and should only be used for guidance, measure your scenario for proper results.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With