Random Numbers help

Started by
6 comments, last by alvaro 12 years, 3 months ago
Hello.
Im having some trouble with the random numbers in c#.
Im creating a basic random loot system that I will use in a text based dungeon crawler, but i need some help.

At the moment the system only supports three kinds of loot: weapon, armor and poitions. To determine what type of loot to create I generate a random number between 1-3 and use in a switchstatement to aply the loot.
I have created a Dice.cs class that handles all random numbers in form of different dice rolls (D12, D20...) and a LootProcessor.cs class that creates random loot.
I use a D3 to get the loot typ, but when I call it from the LootProcessor I never get the number "3".

Dice.cs
public static int RollD3()
{
Random D3 = new Random();
int d3Result = D3.Next(1, 4);
return d3Result;
}


LootProcessor.cs
static string LootType()
{
int lootNum;
lootNum = Dice.RollD3();
switch (lootNum)
{
case 1:
lootResult = weapon;
Output();
break;
case 2:
lootResult = armor;
Output();
break;
case 3:
lootResult = potion;
Output();
break;
default:
Output();
break;
}
return lootResult;
}


If I call the Dice.RollD3() from the program class I get values from 1-3 but when I call it from LootProcessor.cs it only generates from 1-2. I have done some testing and it never generated potions from loot class, only lots of weapons and armor.
Please help me understand this.

Thanks
Advertisement
You generally shouldn't construct a new Random object every time you need a random number. You are supposed to have a Random object around where you call Next multiple times.

That being said, it's a bit weird that the first number out of a fresh Random object would never be a 3. If I were you, I would condense this to the simplest possible program that shows the problem (it should be like 20 lines of code) and post the complete program so others can try to reproduce the issue.
The default .NET random number generator can occasionally cause these artifacts, especially for small numbers.

I've not seen the behavior where it would generate different results depending on callsite. Probably some trick caused by new Random() and the accuracy of your local clock.

You generally shouldn't construct a new Random object every time you need a random number. You are supposed to have a Random object around where you call Next multiple times.


That helped.
Thank you alvaro

static Random D3 = new Random();
public static int RollD3()
{
int d3Result = D3.Next(1, 4);
return d3Result;
}
[sup]Another quick suggestion.[/sup]

Randoms that small don't really seem to be that random. So what I would suggest is using a much larger number that is divisible by three. Then get a random with it as the max, then do a % on the number and add one. Should give you better randoms.
I have been generating randoms in my game.

It seems to help to have one global static random object to use.

If you declare a new random it gets seeded, and in my case caused problems.

To really test out random number generation...

Open a console project and call the program.cs constructor to generate 30 randoms of 1,4 and console writeline (i) after every loop.

Run the console app without debugging, and you can see all of the random numbers which were generated.

To really test out random number generation...

Open a console project and call the program.cs constructor to generate 30 randoms of 1,4 and console writeline (i) after every loop.

To really test out a random generator, you would want to be generating far more than 30 values. The first problem is that people are really bad at recognising random signals. People are biased towards interpreting repetition as non-random.

One key aspect of psuedo-random number generation is that all outputs should be reasonably likely, over a large number of invocations. That is, the generator should avoid bias in its output.

Here is a quick test program I threw together to demonstrate how you might test for such bias, using the default range function, and two simple implementations of such a function:



using System;

public class Test {

public static void Main(string [] args) {

if(args.Length != 2) {
Console.WriteLine("Usage: <number of digits> <number of iterations>");
return;
}

int n = int.Parse(args[0]);
int iterations = int.Parse(args[1]);

Random random = new Random();

int [] frequency;

Console.WriteLine("-------------------------------");
Console.WriteLine("Default random range generation");
Console.WriteLine("-------------------------------");

frequency = new int[n];
for(int i = 0 ; i < iterations ; ++i) {
int index = random.Next(0, n);
frequency[index]++;
}

for(int i = 0 ; i < n ; ++i) {
Console.WriteLine("Frequency of number " + i + ": " + (frequency * 100.0f / iterations) + "%");
}

Console.WriteLine("-------------------------------");
Console.WriteLine("Explicit use of low order bits");
Console.WriteLine("-------------------------------");

frequency = new int[n];
for(int i = 0 ; i < iterations ; ++i) {
int index = random.Next() % n;
frequency[index]++;
}

for(int i = 0 ; i < n ; ++i) {
Console.WriteLine("Frequency of number " + i + ": " + (frequency * 100.0f / iterations) + "%");
}

Console.WriteLine("-------------------------------");
Console.WriteLine("Casting next double to int");
Console.WriteLine("-------------------------------");

frequency = new int[n];
for(int i = 0 ; i < iterations ; ++i) {
int index = (int)(random.NextDouble() * n);
frequency[index]++;
}

for(int i = 0 ; i < n ; ++i) {
Console.WriteLine("Frequency of number " + i + ": " + (frequency * 100.0f / iterations) + "%");
}

}

}


Here is the output on my Linux machine (i.e. using mono, not Microsoft's implementation, YMMV):


user@host:~$ c# test.cs && ./test.exe 10 10000000
-------------------------------
Default random range generation
-------------------------------
Frequency of number 0: 9.99622%
Frequency of number 1: 9.99243%
Frequency of number 2: 10.00303%
Frequency of number 3: 10.02384%
Frequency of number 4: 9.98622%
Frequency of number 5: 9.9946%
Frequency of number 6: 10.00531%
Frequency of number 7: 10.00188%
Frequency of number 8: 10.00555%
Frequency of number 9: 9.99092%
-------------------------------
Explicit use of low order bits
-------------------------------
Frequency of number 0: 9.92869%
Frequency of number 1: 10.0747%
Frequency of number 2: 9.94714%
Frequency of number 3: 10.06481%
Frequency of number 4: 9.93947%
Frequency of number 5: 10.07618%
Frequency of number 6: 9.92547%
Frequency of number 7: 10.04247%
Frequency of number 8: 9.93499%
Frequency of number 9: 10.06608%
-------------------------------
Casting next double to int
-------------------------------
Frequency of number 0: 9.99552%
Frequency of number 1: 9.9953%
Frequency of number 2: 10.00841%
Frequency of number 3: 9.98883%
Frequency of number 4: 9.99405%
Frequency of number 5: 9.99853%
Frequency of number 6: 10.021%
Frequency of number 7: 10.01375%
Frequency of number 8: 9.99331%
Frequency of number 9: 9.9913%

Note that the middle block is far worse than the other two, it appears to be biased in favour of odd numbers. The middle block is, unfortunately, also the naive approach that many use when trying to generate random values in a given range.
The problem with the non-randomness of low order bits doesn't exist in any decent PRNG, like say the Mersenne twister. It's unfortunate that a modern platform provides such a crappy pseudorandom number generator by default.

This topic is closed to new replies.

Advertisement