1

I have this extension method that, given a minimum and maximum double, generates a double between them.

    public static double NextDouble(this Random random, double minValue, double maxValue)
    {
        return random.NextDouble() * (maxValue - minValue) + minValue;  
    }

I mainly use this extension method to generate random dollar amounts, and sometimes 0 dollars is an OK value! That being said, I need to increase the odds of returning a 0. More specifically, if I try the following:

        Random rando = new Random();
        List<double> doubles = new List<double>();
        for (int i = 0; i < 100000; i++)
        {
            double d = rando.NextDouble(0, .25);
            Console.WriteLine(d.ToString());               
        }

I don't get a single zero.

A less than ideal solution I thought of is I can just catch every value less than 1 and return 0 instead.

public static double NextDouble(this Random random, double minValue, double maxValue)
    {
        double d = random.NextDouble() * (maxValue - minValue) + minValue;

        if (d < 1)
        {
            return 0;
        }

        return d;
    }

This obviously removes the ability to return values less than 1 (.25, .50, .125, etc..). I'm looking for some clever ways around this!

Talen Kylon
  • 1,908
  • 7
  • 32
  • 60
  • Possible duplicate of [Random Gaussian Variables](https://stackoverflow.com/questions/218060/random-gaussian-variables) – Thomas Weller Sep 20 '17 at 19:19
  • 8
    A simple way of approaching this is to generate two random numbers: the first to determine if you return 0, and if not, you return the second number. Say for instance you want a 5% chance of returning zero. Generate a random integer between 1 and 100 inclusive, and if its 5 or less, simply return zero. –  Sep 20 '17 at 19:21
  • I would agree with @Amy – ahmet Sep 20 '17 at 19:23
  • @Amy I just wrote an answer that was basically the same as your comment (which I discarded after spotting your comment). I guess it would be a good idea if you formulate your comment as an answer – Ronald Sep 20 '17 at 19:24
  • I guess so... I didn't really expect it to answer the question. It was more of a lightbulb moment. I'll write it into an answer, as suggested. –  Sep 20 '17 at 19:25
  • 4
    Getting absolute 0 has a very low probablity (8 zero bytes). But you can check if it is *close enough to 0* `var ok = Enumerable.Range(0, 100000).Select(x => random.NextDouble() * (0.25-0.0)+0.0).Any(d => Math.Abs(d-0.0)<0.00001);` returns true in my case – L.B Sep 20 '17 at 19:29

3 Answers3

4

A simple way of approaching this is to generate two random numbers: the first to determine if you return 0, and if not, you return the second number. Say for instance you want a 5% chance of returning zero. Generate a random integer between 1 and 100 inclusive, and if its 5 or less, simply return zero.

if (minValue <= 0.0 && 0.0 <= maxValue) 
{
    var shouldWeReturnZero = rando.Next(1, 101) <= 5;
    if (shouldWeReturnZero) 
        return 0;
}

Otherwise, generate the actual random number using the code you already have.

  • I love this solution! However, I think an additional check will need to be included in the case that if the minimum value is not 0, we don't want to return a 0! – Talen Kylon Sep 20 '17 at 19:29
  • I think he means that the minimum of the actual random number is not necessarily 0. So only give a percent chance of 0 if the range of desired values includes 0. – Blake Thingstad Sep 20 '17 at 19:32
  • If we had double d = rando.NextDouble(5, 10); then we wouldn't want to return a 0, only a number between 5 and 10 – Talen Kylon Sep 20 '17 at 19:33
  • 1
    Why in the world was my answer downvoted? Please explain. –  Sep 20 '17 at 19:34
1

What you might want to do is instead of generating a random double, generate a random int and let it represent cents.

that way, 0 will be just as likely as any other amount of cents. When showing them to the user, you can display them as dollars.

        var random = new Random();

        for (var i = 0; i < 1000; i++)
        {
            var cents = random.Next(0, 200);

            Console.WriteLine("Dollar amount:  ${0:0}.{1:00}", cents / 100, cents % 100);
        }
        Console.ReadKey(false);
1

So the reason why you are getting no zeroes is that the probability of getting an absolute zero when generating a double is very very very unlikely. If you have a 32-bit floating number, the probability of getting a zero is somewhere around the number 1/2^32. If you want to know more, check out https://en.wikipedia.org/wiki/Single-precision_floating-point_format and how floating numbers are constructed from memory.

In your case I would create a floor-function that instead of flooring to integers, it does so in steps of 0.25. So, a floor function takes any floating number and removes the decimals so what's left is an integer, for example from 1.7888 to 1. You want something that is a bit less rough, so if you insert a value of 1.7888, it would spit out 1.75.

maxpaj
  • 6,029
  • 5
  • 35
  • 56