Ich bin etwas spät dran, aber ich musste eine allgemeine Lösung implementieren, und es stellte sich heraus, dass keine der Lösungen meine Anforderungen erfüllen kann.
Die akzeptierte Lösung ist gut für kleine Bereiche; jedoch maximum - minimum
kann es sich unendlich für große Bereiche. Eine korrigierte Version kann also diese Version sein:
public static double NextDoubleLinear(this Random random, double minValue, double maxValue)
{
// TODO: some validation here...
double sample = random.NextDouble();
return (maxValue * sample) + (minValue * (1d - sample));
}
Dies erzeugt auch zwischen double.MinValue
und schöne Zufallszahlen double.MaxValue
. Dies führt jedoch zu einem weiteren "Problem", das in diesem Beitrag gut dargestellt wird : Wenn wir so große Bereiche verwenden, erscheinen die Werte möglicherweise zu "unnatürlich". Zum Beispiel double.MaxValue
lagen nach der Erzeugung von 10.000 zufälligen Doppelwerten zwischen 0 und allen Werten zwischen 2,9579E + 304 und 1,7976E + 308.
Also habe ich auch eine andere Version erstellt, die Zahlen auf einer logarithmischen Skala generiert:
public static double NextDoubleLogarithmic(this Random random, double minValue, double maxValue)
{
// TODO: some validation here...
bool posAndNeg = minValue < 0d && maxValue > 0d;
double minAbs = Math.Min(Math.Abs(minValue), Math.Abs(maxValue));
double maxAbs = Math.Max(Math.Abs(minValue), Math.Abs(maxValue));
int sign;
if (!posAndNeg)
sign = minValue < 0d ? -1 : 1;
else
{
// if both negative and positive results are expected we select the sign based on the size of the ranges
double sample = random.NextDouble();
var rate = minAbs / maxAbs;
var absMinValue = Math.Abs(minValue);
bool isNeg = absMinValue <= maxValue ? rate / 2d > sample : rate / 2d < sample;
sign = isNeg ? -1 : 1;
// now adjusting the limits for 0..[selected range]
minAbs = 0d;
maxAbs = isNeg ? absMinValue : Math.Abs(maxValue);
}
// Possible double exponents are -1022..1023 but we don't generate too small exponents for big ranges because
// that would cause too many almost zero results, which are much smaller than the original NextDouble values.
double minExponent = minAbs == 0d ? -16d : Math.Log(minAbs, 2d);
double maxExponent = Math.Log(maxAbs, 2d);
if (minExponent == maxExponent)
return minValue;
// We decrease exponents only if the given range is already small. Even lower than -1022 is no problem, the result may be 0
if (maxExponent < minExponent)
minExponent = maxExponent - 4;
double result = sign * Math.Pow(2d, NextDoubleLinear(random, minExponent, maxExponent));
// protecting ourselves against inaccurate calculations; however, in practice result is always in range.
return result < minValue ? minValue : (result > maxValue ? maxValue : result);
}
Einige Tests:
Hier sind die sortierten Ergebnisse der Erzeugung von 10.000 zufälligen Doppelzahlen zwischen 0 und Double.MaxValue
mit beiden Strategien. Die Ergebnisse werden mit logarithmischer Skala angezeigt:
Obwohl die linearen Zufallswerte auf den ersten Blick falsch zu sein scheinen, zeigen die Statistiken, dass keiner von ihnen "besser" ist als der andere: Selbst die lineare Strategie hat eine gleichmäßige Verteilung und die durchschnittliche Differenz zwischen den Werten ist bei beiden Strategien ziemlich gleich .
Das Spielen mit verschiedenen Bereichen hat mir gezeigt, dass die lineare Strategie mit einem Bereich zwischen 0 und ushort.MaxValue
einem "angemessenen" Mindestwert von 10,78294704 "vernünftig" wird (für den ulong
Bereich betrug der Mindestwert 3,03518E + 15 ; int
: 353341). Dies sind die gleichen Ergebnisse beider Strategien, die mit unterschiedlichen Maßstäben angezeigt werden:
Bearbeiten:
Kürzlich habe ich meine Bibliotheken zu Open Source gemacht. Sie können sich die RandomExtensions.NextDouble
Methode mit der vollständigen Validierung ansehen .