2

The following code generates a double number in the range [0,1) which means that 1 is exclusive.

var random = new Random();
random.NextDouble();

I am looking for some smart way to generate a random double number in the range [0,1]. It means that 1 is inclusive. I know that the probability of generating 0 or 1 is really low, but imagine that I want to implement a correct mathematical function that requires from me the inclusive limits. How can I do it?

The question is: What is the correct way of generating random in the range [0,1]. If there is no such way, I would love to learn it also.

user2864740
  • 60,010
  • 15
  • 145
  • 220
Mateusz Kopij
  • 93
  • 1
  • 12
  • 3
    Probability of having `1` (a *point* on the *continuous segment* `[0..1]`) is **0**, that's why you can keep `random.NextDouble();` – Dmitry Bychenko Mar 17 '21 at 19:56
  • 2
    @Dimitry Wouldn’t that also imply the probability of generating 0 in [0..1] is also .. 0? Yet, 0 is in [0, 1) and *will* be returned given enough samples. Double (and the random source) has a finite set of values. – user2864740 Mar 17 '21 at 20:00
  • Doubles don't have exact values. If you want exact values, you should use Decimals instead. Check out this post for a random decimal implementation: https://stackoverflow.com/questions/609501/generating-a-random-decimal-in-c-sharp – Connell.O'Donnell Mar 17 '21 at 20:07
  • 1
    A double *can* represent the value 1.0 exactly. As decimal still can’t represent all the numbers in [0,1] it seems a bit silly to recommend using a decimal in context of the question, especially as it doesn’t change the range of NextDouble (or a hypothetical NextDecimal). – user2864740 Mar 17 '21 at 20:12
  • (While I’ve never wanted the requested behavior, the request should still be considered for what it is. If this sort of PRNG ‘control’ is desired, Math.Random probably also has other failings.) – user2864740 Mar 17 '21 at 20:17
  • 1
    There is no correct way of producing what you want if we assume that `r.NextDouble()` can produce *any* double value. You can make up several schemes to get the value 1 as a possible output, but the resulting chance of getting that number will be skewed. If we assume that `r.NextDouble()` is not going to be able to produce *every* possible double value from 0 to 1, you can use `r.Next(int.MaxInt) / (double)(int.MaxInt - 1)`. – Lasse V. Karlsen Mar 17 '21 at 20:44
  • See https://stackoverflow.com/a/52439575. It doesn't really answer the question where it was posted, but it seems to address this question. – Peter Duniho Mar 17 '21 at 22:36
  • Use `Random.NextBytes()` and feed that to the solution in the first duplicate. Or check the other duplicates. – Peter Duniho Mar 17 '21 at 22:45

6 Answers6

2

The Random.Next method returns an integer value in the range [0..Int32.MaxValue) (the exclusive range-end is denoted by the right parenthesis). So if you want to make the value 1.0 a possible result of the NextDouble method (source code), you could do this:

/// <summary>Returns a random floating-point number that is greater than or equal to 0.0,
/// and less than or equal to 1.0.</summary>
public static double NextDoubleInclusive(this Random random)
{
    return (random.Next() * (1.0 / (Int32.MaxValue - 1)));
}

This fiddle verifies that the expression (Int32.MaxValue - 1) * (1.0 / (Int32.MaxValue - 1)) evaluates to 1.0.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
2

If you want uniform distribution, it‘s harder than it seams. Look how NextDouble is implemented.

There’re ways to produce uniformly distributed numbers in arbitrary intervals, an easy one is selectively discarding some of the generated values. Here’s how I would do that for your problem.

/// <summary>Utility function to generate random 64-bit numbers</summary>
static ulong nextUlong( Random rand )
{
    Span<byte> buffer = stackalloc byte[ 8 ];
    rand.NextBytes( buffer );
    return BitConverter.ToUInt64( buffer );
}

/// <summary>Generate a random number in [ 0 .. +1 ] interval, inclusive.</summary>
public static double nextDoubleInclusive( Random rand )
{
    // We need uniformly distributed integer in [ 0 .. 2^53 ]
    // The interval contains ( 2^53 + 1 ) distinct values.

    // The complete range of ulong is [ 0 .. 2^64 - 1 ], 2^64 distinct values.
    // 2^64 / ( 2^53 + 1 ) is about 2047.99, here's why
    // https://www.wolframalpha.com/input/?i=2%5E64+%2F+%28+2%5E53+%2B+1+%29

    const ulong discardThreshold = 2047ul * ( ( 1ul << 53 ) + 1 );

    ulong src;
    do
    {
        src = nextUlong( rand );
    }
    while( src >= discardThreshold );
    // Got uniformly distributed value in [ 0 .. discardThreshold ) interval
    // Dividing by 2047 gets us a uniformly distributed value in [ 0 ..  2^53 ]
    src /= 2047;

    // Produce the result
    return src * ( 1.0 / ( 1ul << 53 ) );
}
Soonts
  • 20,079
  • 9
  • 57
  • 130
2

After taking a shower, I have conceived of a potential solution based on my understanding of how a random floating point generator works. My solution makes three assumptions, which I believe to be reasonable, however I can not verify if these assumptions are correct or not. Because of this, the following code is purely academic in nature, and I would not recommend its use in practice. The assumptions are as follows:

  1. The distribution of random.NextDouble() is uniform
  2. The difference between any two adjacent numbers in the range produced by random.NextDouble() is a constant epsilon e
  3. The maximum value generated by random.NextDouble() is equal to 1 - e

Provided that those three assumptions are correct, the following code generates random doubles in the range [0, 1].

// For the sake of brevity, we'll omit the finer details of reusing a single instance of Random
var random = new Random();

double RandomDoubleInclusive() {
    double d = 0.0;
    int i = 0;

    do {
        d = random.NextDouble();
        i = random.Next(2);
    } while (i == 1 && d > 0)
    
    return d + i;
}

This is somewhat difficult to conceptualize, but the essence is somewhat like the below coin-flipping explanation, except instead of a starting value of 0.5, you start at 1, and if at any point the sum exceeds 1, you restart the entire process.

From an engineering standpoint, this code is a blatant pessimization with little practical advantage. However, mathematically, provided that the original assumptions are correct, the result will be as mathematically sound as the original implementation.

Below is the original commentary on the nature of random floating point values and how they're generated.

Original Reply:

Your question carries with it a single critical erroneous assumption: Your use of the word "Correct". We are working with floating point numbers. We abandoned correctness long ago.

What follows is my crude understanding of how a random number generator produces a random floating point value.

You have a coin, a sum starting at zero, and a value starting at one half (0.5).

  1. Flip the coin.
  2. If heads, add the value to the sum.
  3. Half the value.
  4. Repeat 23 times.

You have just generated a random number. Here are some properties of the number (for reference, 2^23 is 8,388,608, and 2^(-23) is the inverse of that, or approximately 0.0000001192):

  • The number is one of 2^23 possible values
  • The lowest value is 0
  • The highest value is 1 - 2^(-23);
  • The smallest difference between any two potential values is 2^(-23)
  • The values are evenly distributed across the range of potential values
  • The odds of getting any one value are completely uniform across the range
  • Those last two points are true regardless of how many times you flip the coin
  • The process for generating the number was really really easy

That last point is the kicker. It means if you can generate raw entropy (i.e. perfectly uniform random bits), you can generate an arbitrarily precise number in a very useful range with complete uniformity. Those are fantastic properties to have. The only caveat is that it doesn't generate the number 1.

The reason that caveat is seen as acceptable is because every other aspect of the generation is so damned good. If you're trying to get a high precision random value between 0 and 1, chances are you don't actually care about landing on 1 any more than you care about landing on 0.38719, or any other random number in that range.

While there are methods for getting 1 included in your range (which others have stated already), they're all going to cost you in either speed or uniformity. I'm just here to tell you that it might not actually be worth the tradeoff.

VPellen
  • 1,071
  • 5
  • 10
  • Thank you for your comment. I apologise for not being clear enough. I am aware of the tradeoff you described above. What I had in mind when asking the question is my uncertenty if I am missing something. I am not an expert in this area. I was not able to find on the internet anyone asking a question of what is a good/correct/working way of implementing random in the range [0,1]. I realised that there must be someone that solved this problem before. That's why I am asking it on a forum. – Mateusz Kopij Mar 17 '21 at 22:35
  • I understand, and apologize - My goal was not to provide an explicit solution, but rather to explain why the generator even does what it does, for the benefit of any future readers. Your problem is one I've also encountered in the past. I wish you luck and I hope you find a suitable solution. – VPellen Mar 17 '21 at 22:43
  • 1
    To add more to my comment above, I often implement solutions for various mathematical problems. Mathematical problems are well defined, just like a random in the range [0,1]. When I solve such a problem w/o following all constraints, I feel like I haven't really solved the problem; I just simplified it. That's why I am pleased to see smarter people sharing how they would solve the defined problem. – Mateusz Kopij Mar 17 '21 at 22:43
  • After a shower I managed to actually come up with a solution that I believe is mathematically sound. – VPellen Mar 18 '21 at 00:26
1

Usually, knowing that NextDouble() has a finite range, we multiply the value to suit the range we need.

For this reason it is common to create your own wrapper to produce the next business value when the built in logic does not meet your requirements.

For this particular example, why not just post process the result, when zero get the value from Next(0,2)

public static double NextInclude1(this Random rand = null)
{
    rand = rand ?? new Random();
    var result = rand.NextDouble();
    if (result == 0) result = rand.Next(0,2);
    return result;
}

You can implement your own bias for 0 or 1 as a result by varying the comparison to zero, if you do that though you are likely to create an exclusion range, so after the comparison you may need to return the next NextDouble()

public static double NextInclude1(this Random rand = null)
{
    rand = rand ?? new Random();
    var result = rand.NextDouble();
    if (result < 0.2) 
        result = rand.Next(0,2);
    else
        result = rand.NextDouble();
    return result;
}

This particular example results in an overall bias for 0, it's up to you to determine the specific parameters that you would accept, overall NextDouble() is your base level tool for most of your custom Random needs.

Chris Schaller
  • 13,704
  • 3
  • 43
  • 81
  • 3
    Have you verified the distribution of the results for this? There is a 1/N chance to get 0, and then there's a 1/2 chance to get 0 or 1. This means all numbers will have a 1/N chance to be produced, except 0 or 1 which will have a chance of 1/N*2. – Lasse V. Karlsen Mar 17 '21 at 20:35
  • If you're interested in a specific distribution of the results to that level, and you can't easily compensate, then don't use `Random` I guess. – Chris Schaller Mar 17 '21 at 20:43
  • @LasseV.Karlsen provided N is big enough (and it is) it wouldn't really matter – bashis Mar 17 '21 at 20:44
  • @LasseV.Karlsen watch the first example closely: 0 or 1 are generated only if `NextDouble()` returns exactly 0 which is highly unlikely. – bashis Mar 17 '21 at 20:47
  • 2
    My previous comment, which I deleted, was more appropriate for the other answer here, so disregard that, but with your latest reply, yes, it is highly unlikely, but then 0 and 1 will be half as likely as highly unlikely. My point is not that the solution isn't usable, it's just that you need to be aware of its limitations. Both 0 and 1 will have half the chance of being produced as any other number between 0 and 1 – Lasse V. Karlsen Mar 17 '21 at 20:49
  • 1
    @LasseV.Karlsen that is correct, but it really depends on the precision you are trying to achieve. As @ChrisSchaller said you probably should not be using `System.Random` at all if that kind of precision is the case for you. – bashis Mar 17 '21 at 20:53
0

This definitely works, You can check the distribution here https://dotnetfiddle.net/SMMOrM

Random random = new Random();
double result = (int)(random.NextDouble() * 10) > 4
    ? random.NextDouble()
    : 1 - random.NextDouble();

Update

Agree with Snoot, this version would return 0 and 1 twice less often as other values

Mocas
  • 1,403
  • 13
  • 20
  • 2
    Your version returns 0 and 1 twice less often than the rest of the values in the interval. – Soonts Mar 17 '21 at 21:23
-2

Easy, you can do this

var random = new Random();
var myRandom = 1 - Math.Abs(random.NextDouble() - random.NextDouble());

update

Sorry, this won't generate normal distribution of results, where they will tend to be higher ones, close to 1.

Mocas
  • 1,403
  • 13
  • 20
  • Have you verified the distribution of the results for this? This will have more like a gaussian curve distribution than a flat distribution. – Lasse V. Karlsen Mar 17 '21 at 20:35
  • Yes, just did, unfortunately it tend towards higher number, so probability of numbers closer to 1 is higher. https://dotnetfiddle.net/7W7U2i – Mocas Mar 17 '21 at 21:00