web-dev-qa-db-de.com

Beste Möglichkeit, einen zufälligen Float in C # zu generieren

Was ist der beste Weg, um einen zufälligen Float in C # zu generieren?

Update: Ich möchte zufällige Gleitkommazahlen von float.Minvalue bis float.Maxvalue. Ich verwende diese Zahlen beim Unit-Test einiger mathematischer Methoden.

48
KrisTrip

Beste Annäherung, keine verrückten Werte, in Bezug auf die darstellbaren Intervalle auf der Fließkommazahllinie verteilt ("einheitlich" entfernt, da in Bezug auf eine durchgehende Zahlenlinie entschieden ungleichmäßig ist):

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 für subnormale Schwimmer

Warnung: erzeugt auch positive Unendlichkeit! Wählen Sie den Exponenten von 127, um auf der sicheren Seite zu sein.

Ein weiterer Ansatz, der Ihnen einige verrückte Werte (gleichmäßige Verteilung von Bitmustern) liefert, die möglicherweise für das Fuzzing nützlich sind:

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

Eine Verbesserung gegenüber der Vorgängerversion ist diese, die keine "verrückten" Werte erzeugt (weder Unendlich noch NaN) und trotzdem schnell ist (auch bezüglich der darstellbaren Intervalle auf der Fließkommazahllinie verteilt):

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

Am wenigsten sinnvoller Ansatz:

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

Fließkommazahllinie von: Handbuch für Entwickler von Intel Architecture Software Volume 1: Basisarchitektur. Die Y-Achse ist logarithmisch (Basis-2), da aufeinanderfolgende binäre Fließkommazahlen sich nicht linear unterscheiden. 

Comparison of distributions, logarithmic Y-axis

59
user7116

Gibt es einen Grund, Random.NextDouble nicht zu verwenden und dann in float umzuwandeln? Dadurch erhalten Sie einen Float zwischen 0 und 1.

Wenn Sie eine andere Form des "Besten" wünschen, müssen Sie Ihre Anforderungen angeben. Beachten Sie, dass Random nicht für sensible Themen wie Finanzen oder Sicherheit verwendet werden sollte. Sie sollten im Allgemeinen eine vorhandene Instanz in Ihrer gesamten Anwendung oder eine pro Thread wiederverwenden (da Random nicht threadsicher ist).

BEARBEITEN: Wie in Kommentaren vorgeschlagen, um dies in einen Bereich von float.MinValue zu konvertieren, 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: Jetzt haben Sie erwähnt, dass es sich um Unit-Tests handelt. Ich bin mir nicht sicher, ob dies ein idealer Ansatz ist. Sie sollten wahrscheinlich stattdessen mit konkreten Werten testen - stellen Sie sicher, dass Sie mit Stichproben in den jeweiligen Kategorien testen - Unendlich, NaNs, Denormal-Zahlen, sehr große Zahlen, Null usw.

24
Jon Skeet

Noch eine Version ... (Ich denke, diese ist ziemlich gut)

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

Ich denke das...

  • ist der zweitschnellste (siehe Benchmarks)
  • ist gleichmäßig verteilt

Und noch eine Version ... (nicht so gut, aber trotzdem posten)

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

Ich denke das...

  • ist der schnellste (siehe Benchmarks)
  • ist jedoch gleichmäßig verteilt, da Next () ein zufälliger 31-Bit-Wert ist und nur 2 ^ 31-Werte zurückgibt. (50% der Nachbarwerte haben den gleichen Wert)

Testen der meisten Funktionen auf dieser Seite: (i7, Release, ohne Debuggen, 2 ^ 28 Schleifen)

 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
5
Sunsetquest

Ich bin etwas anders vorgegangen als andere 

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

Die Kommentare erklären, was ich mache. Holen Sie sich das nächste Double, konvertieren Sie diese Zahl in einen Wert zwischen -1 und 1 und multiplizieren Sie diese mit float.MaxValue.

1
Anthony Pegram

Hier ist ein anderer Weg, auf den ich gekommen bin: Nehmen wir an, Sie möchten einen Float zwischen 5,5 und 7 mit 3 Dezimalstellen.

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

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

Auf diese Weise erhalten Sie immer eine größere Zahl als 5,5 und eine kleinere Zahl als 7.

0
Qedized

Ich bevorzuge die Verwendung des folgenden Codes, um eine Dezimalzahl bis zur ersten Dezimalstelle zu generieren. Sie können die 3. Zeile kopieren, um weitere Zahlen nach dem Komma hinzuzufügen, indem Sie diese Zahl in der Zeichenfolge "kombiniert" anhängen. Sie können den minimalen und maximalen Wert einstellen, indem Sie 0 und 9 auf Ihren bevorzugten Wert ändern.

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

Ich hoffe es hat dir geholfen.

0
Waheed Sattar

Eine andere Lösung ist dies zu tun:

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;
}
0
Simon Mourier