Best way to generate a random float in C# [closed]

2020-01-30 03:02发布

问题:


Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.

Closed 2 years ago.

What is the best way to generate a random float in C#?

Update: I want random floating point numbers from float.Minvalue to float.Maxvalue. I am using these numbers in unit testing of some mathematical methods.

回答1:

Best approach, no crazed values, distributed with respect to the representable intervals on the floating-point number line (removed "uniform" as with respect to a continuous number line it is decidedly non-uniform):

static float NextFloat(Random random)
{
    double mantissa = (random.NextDouble() * 2.0) - 1.0;
    // choose -149 instead of -126 to also generate subnormal floats (*)
    double exponent = Math.Pow(2.0, random.Next(-126, 128));
    return (float)(mantissa * exponent);
}

(*) ... check here for subnormal floats

Warning: generates positive infinity as well! Choose exponent of 127 to be on the safe side.

Another approach which will give you some crazed values (uniform distribution of bit patterns), potentially useful for fuzzing:

static float NextFloat(Random random)
{
    var buffer = new byte[4];
    random.NextBytes(buffer);
    return BitConverter.ToSingle(buffer,0);
}

An improvement over the previous version is this one, which does not create "crazed" values (neither infinities nor NaN) and is still fast (also distributed with respect to the representable intervals on the floating-point number line):

public static float Generate(Random prng)
{
    var sign = prng.Next(2);
    var exponent = prng.Next((1 << 8) - 1); // do not generate 0xFF (infinities and NaN)
    var mantissa = prng.Next(1 << 23);

    var bits = (sign << 31) + (exponent << 23) + mantissa;
    return IntBitsToFloat(bits);
}

private static float IntBitsToFloat(int bits)
{
    unsafe
    {
        return *(float*) &bits;
    }
}

Least useful approach:

static float NextFloat(Random random)
{
    // Not a uniform distribution w.r.t. the binary floating-point number line
    // which makes sense given that NextDouble is uniform from 0.0 to 1.0.
    // Uniform w.r.t. a continuous number line.
    //
    // The range produced by this method is 6.8e38.
    //
    // Therefore if NextDouble produces values in the range of 0.0 to 0.1
    // 10% of the time, we will only produce numbers less than 1e38 about
    // 10% of the time, which does not make sense.
    var result = (random.NextDouble()
                  * (Single.MaxValue - (double)Single.MinValue))
                  + Single.MinValue;
    return (float)result;
}

Floating point number line from: Intel Architecture Software Developer's Manual Volume 1: Basic Architecture. The Y-axis is logarithmic (base-2) because consecutive binary floating point numbers do not differ linearly.



回答2:

Any reason not to use Random.NextDouble and then cast to float? That will give you a float between 0 and 1.

If you want a different form of "best" you'll need to specify your requirements. Note that Random shouldn't be used for sensitive matters such as finance or security - and you should generally reuse an existing instance throughout your application, or one per thread (as Random isn't thread-safe).

EDIT: As suggested in comments, to convert this to a range of float.MinValue, float.MaxValue:

// Perform arithmetic in double type to avoid overflowing
double range = (double) float.MaxValue - (double) float.MinValue;
double sample = rng.NextDouble();
double scaled = (sample * range) + float.MinValue;
float f = (float) scaled;

EDIT: Now you've mentioned that this is for unit testing, I'm not sure it's an ideal approach. You should probably test with concrete values instead - making sure you test with samples in each of the relevant categories - infinities, NaNs, denormal numbers, very large numbers, zero, etc.



回答3:

One more version... (I think this one is pretty good)

static float NextFloat(Random random)
{
    (float)(float.MaxValue * 2.0 * (rand.NextDouble()-0.5));
}

//inline version
float myVal = (float)(float.MaxValue * 2.0 * (rand.NextDouble()-0.5));

I think this...

  • is the 2nd fastest (see benchmarks)
  • is evenly distributed

And One more version...(not as good but posting anyway)

static float NextFloat(Random random)
{
    return float.MaxValue * ((rand.Next() / 1073741824.0f) - 1.0f);
}

//inline version
float myVal = (float.MaxValue * ((rand.Next() / 1073741824.0f) - 1.0f));

I think this...

  • is the fastest (see benchmarks)
  • is evenly distributed however because Next() is a 31 bit random value it will only return 2^31 values. (50% of the neighbor values will have the same value)

Testing of most of the functions on this page: (i7, release, without debug, 2^28 loops)

 Sunsetquest1: min: 3.402823E+38  max: -3.402823E+38 time: 3096ms
 SimonMourier: min: 3.402823E+38  max: -3.402819E+38 time: 14473ms
 AnthonyPegram:min: 3.402823E+38  max: -3.402823E+38 time: 3191ms
 JonSkeet:     min: 3.402823E+38  max: -3.402823E+38 time: 3186ms
 Sixlettervar: min: 1.701405E+38  max: -1.701410E+38 time: 19653ms
 Sunsetquest2: min: 3.402823E+38  max: -3.402823E+38 time: 2930ms


回答4:

I took a slightly different approach than others

static float NextFloat(Random random)
{
    double val = random.NextDouble(); // range 0.0 to 1.0
    val -= 0.5; // expected range now -0.5 to +0.5
    val *= 2; // expected range now -1.0 to +1.0
    return float.MaxValue * (float)val;
}

The comments explain what I'm doing. Get the next double, convert that number to a value between -1 and 1 and then multiply that with float.MaxValue.



回答5:

Another solution is to do this:

static float NextFloat(Random random)
{
    float f;
    do
    {
        byte[] bytes = new byte[4];
        random.NextBytes(bytes);
        f = BitConverter.ToSingle(bytes, 0);
    }
    while (float.IsInfinity(f) || float.IsNaN(f));
    return f;
}


回答6:

Here is another way that I came up with: Let's say you want to get a float between 5.5 and 7, with 3 decimals.

float myFloat;
int myInt;
System.Random rnd = new System.Random();

void GenerateFloat()
{
myInt = rnd.Next(1, 2000);
myFloat = (myInt / 1000) + 5.5f;
}

That way you will always get a bigger number than 5.5 and a smaller number than 7.



回答7:

I prefer using the following code to generate a decimal number up to fist decimal point. you can copy paste the 3rd line to add more numbers after decimal point by appending that number in string "combined". You can set the minimum and maximum value by changing the 0 and 9 to your preferred value.

Random r = new Random();
string beforePoint = r.Next(0, 9).ToString();//number before decimal point
string afterPoint = r.Next(0,9).ToString();//1st decimal point
//string secondDP = r.Next(0, 9).ToString();//2nd decimal point
string combined = beforePoint+"."+afterPoint;
decimalNumber= float.Parse(combined);
Console.WriteLine(decimalNumber);

I hope that it helped you.