3

As I new to C# I don't really want to mess around with the Random() for a long time trying to get what I want, and I also want to know it does what I need it to do without giving it a really long amount of time in testing. How can I get a 0.5% chance of something (1/200) with a random? would this code would? How random is Random really.. this isn't a question of "how random is random" so don't go posting duplicates, but its a question on how I can do this.

My question is not "How random is Random, its if this code is the best way to do the job, and will it achieve what I am trying to achieve."

var random = new Random();
var randomNumber = random.Next(1, 200);

if (randomNumber == 87)
{
    // I can put any number inbetween 1 and 200, will this work?
    // If we reach this if statement we have got a 0.5 chance?
}
Liam Savage
  • 167
  • 12
  • `Random` isn't. No PC can manage the abstract human concept of Randomness. Instead, the closest you get is an algorithm to simulate random selection, usually based on the system date and time in milliseconds as a seed, which gives close enough results over extended use to an abstract "chance". – CDove Mar 06 '17 at 13:21
  • The most general way to do this is: (1) Express the chance as a normal probability, i.e. a value between 0 and 1. (2) use `if (rng.NextDouble() < probability) ...`. In your case, `probability = 0.005;` Note that `Random.NextDouble()` will never return `1.0`; its range is the half-open interval `[0, 1)` – Matthew Watson Mar 06 '17 at 13:24
  • 1
    @MatthewWatson Instead of that, I would probably do it like `random.Next(0, 100) < probability`... Internally `Random` works on integers and do some magic to return `double`s. – xanatos Mar 06 '17 at 13:29
  • @MatthewWatson, after reading your comment I was able to finally understand the OP question. Rather post it as an answer. – Sinatr Mar 06 '17 at 13:29
  • 1
    @xanatos The random range for `NextDouble()` is uniformly distributed, and is intended for performing calculations related to a normalized probability (among other things), so I would definitely use `NextDouble()`. Note that `Random` also does some "magic" to return random numbers within a specified range, since internally it's working with a 32-bit random number (for the built-in Random - other implementations of random use up to 128-bit internal random numbers) – Matthew Watson Mar 06 '17 at 13:30
  • 1
    [continued] And indeed, if you look at the implementation of `Random` on Reference Source, you'll see that it uses the same value as returned by `NextDouble()` to implement `Random.Next(int min, int max)` – Matthew Watson Mar 06 '17 at 13:35
  • Possible duplicate of [Generate Random Boolean Probability](http://stackoverflow.com/questions/25275873/generate-random-boolean-probability) – Peter O. Mar 06 '17 at 13:38

1 Answers1

5

Firstly you should convert your chance into a normalized value between 0.0 and 1.0. This is the mathematical notion of a probability.

For your case, this would give you double probability = 0.005;.

Then you can do the following:

if (rng.NextDouble() < probability)
{
    ...

This works because Random.NextDouble() returns a random number evenly distributed within the half-open interval [0.0, 1.0) (i.e. up to but not including 1.0.)

So if your probability is 0.0 the body of the if will never be executed, and if your probability is 1.0 then it will always be executed.

The advantage of using a normalised probability is that it works with any probability, and not just with integral probabilities.

If you do happen to have a percentage probability, you convert it to a normalised one very simply - by dividing it by 100.0.


Addendum:

There's little advantage to using Random.Next(int min, int max) instead, because that only works for integral probabilities. And behind the scenes, Random.Next(int min, int max) is implemented like this:

public virtual int Next(int minValue, int maxValue) {
  if (minValue>maxValue) {
      throw new ArgumentOutOfRangeException("minValue",Environment.GetResourceString("Argument_MinMaxValue", "minValue", "maxValue"));
  }
  Contract.EndContractBlock();

  long range = (long)maxValue-minValue;
  if( range <= (long)Int32.MaxValue) {  
      return ((int)(Sample() * range) + minValue);
  }          
  else { 
      return (int)((long)(GetSampleForLargeRange() * range) + minValue);
  }
}

And NextDouble() is implemented as:

public virtual double NextDouble() {
    return Sample();
}

Note that both these implementations call Sample().

Finally I just want to note that the built-in Random class isn't particularly great - it doesn't have a very long period. I use a RNG based on a 128-bit XOR-Shift which is very fast and generates very "good" random numbers.

(I use one based on this XORSHIFT+ generator.)

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276