tags:

views:

497

answers:

3

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.

+4  A: 

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.

Jon Skeet
I think NextDouble just provides values from 0.0 to 1.0 and I want more range than that (like from float.MinValue to float.MaxValue). Guess I should have specified :)
KrisTrip
Just multiply it by (MaxValue-MinValue) and add MinValue to it? So something like Random.NextDouble *(float.MaxValue-float.MinValue) + float.MinValue
Xzhsh
The distribution of values that produces is bimodal, I believe it has to do with how large the values are in the range.
sixlettervariables
I've finally reasoned out why this method does not produce the expected distribution of values for a binary floating point number. If NextDouble() returns a uniform distribution between 0 and 1, only 10% of the values would be between 0 and 0.1. So only 10% of the time the numbers will be less than 1e37, which is not the expected distribution.
sixlettervariables
@sixlettervariables: Where is this "expected distribution" specified? The distribution I expected was a uniform one, and I see no reason to believe that's not happening here.
Jon Skeet
Fair enough that the expected distribution was omitted, but I would gather a uniform distribution from MinValue to MaxValue would be desired. Since less floating point numbers can be represented the closer you get to MinValue or MaxValue, I would reason less values would be generated there, no?
sixlettervariables
@sixlettervariables: That's not what I'd expect a uniform distribution to mean, no. I'd expect it to mean that the probability of a value within any given interval within the range is the same (as closely as possible) wherever that interval occurs. So the probability of getting a value in the range [0, 10000] is the same as the probability of getting a value in the range [100000, 110000]. I suppose it really depends on whether you view the floating point range as continuous or discrete.
Jon Skeet
Yes, I dusted off ye olde stats book and I'm in agreement for floating point range used as continuous values.
sixlettervariables
Should I instead conclude I've produced a uniform distribution of bit patterns?
sixlettervariables
@sixlettervariables: Yes, that would be reasonable - certainly for the last one. I don't think it's *quite* uniform for the rest, due to denormal numbers - but so long as the OP knows what he's getting by and large, that's probably okay :)
Jon Skeet
I am using both concrete and random values for unit testing. I test a range of concrete values and then create a number of random values to test with as well.
KrisTrip
+16  A: 

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;
    double exponent = Math.Pow(2.0, random.Next(-126, 128));
    return (float)(mantissa * exponent);
}

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);
}

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.

Comparison of distributions, logarithmic Y-axis

sixlettervariables
There doesn't seem to be a BitConverter.GetSingle method. Do you mean ToSingle?
KrisTrip
That would be what I mean...
sixlettervariables
Using random bytes can easily end up with a NaN value, and the distribution may well be non-uniform. (I wouldn't like to predict the distribution.)
Jon Skeet
Agreed, the second one is useful for testing the range of potential floating point values including NaN. It appears this method produces about 0.25%-0.4% NaN.
sixlettervariables
Interestingly, using Single.MinValue/Single.MaxValue as the range produces a pretty poor distribution of numbers.
sixlettervariables
Actually, the second method produces a very nice distribution of floating point numbers (normal, denormal, positive, negative). The first method does not with the full range of values.
sixlettervariables
@sixlettervariables: What sort of distribution do you deem "very nice"? In most cases I'd expect a reasonably uniform distribution to be desirable.
Jon Skeet
@sixlettervariables: The reason yours looks prettier that way is because you've given a logarithmic scale. On a linear scale, I believe NextDouble * range will be uniform.
Jon Skeet
NextDouble * Range lacks values below 1e35ish. I've produced an update way to create floats that are well distributed without NaN.
sixlettervariables
@Jon Skeet: you are correct, however it has no values between -1e35 and 1e35 which makes it less than useful for testing.
sixlettervariables
@sixlettervariables: *No* values in that range? I did a quick test and got about 30 per 100,000 samples - which is roughly what I'd expect, given the range of values available.
Jon Skeet
Another point would be the distance between two consecutive binary floating point numbers is not linear, but log2. I've updated by graphs to reflect this point.
sixlettervariables
Perhaps I've just botched a copy of your code, but I cannot get your method to produce any values anywhere near 0.
sixlettervariables
I think it would be worth specifying what the distribution is, rather than just asserting it's sane :) (In particular, it's *not* what I'd expect.)
Jon Skeet
A: 

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.

Anthony Pegram