Je suis un peu en retard à la fête mais j'avais besoin de mettre en place une solution générale et il s'est avéré qu'aucune des solutions ne peut satisfaire mes besoins.
La solution acceptée est bonne pour les petites plages; cependant, maximum - minimum
peut être l'infini pour les grandes plages. Donc une version corrigée peut être cette version:
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));
}
Cela génère des nombres aléatoires bien même entre double.MinValue
et double.MaxValue
. Mais cela introduit un autre "problème", qui est bien présenté dans cet article : si nous utilisons de si grandes plages, les valeurs peuvent sembler trop "artificielles". Par exemple, après avoir généré 10000 doubles aléatoires entre 0 etdouble.MaxValue
toutes les valeurs étaient comprises entre 2,9579E + 304 et 1,7976E + 308.
J'ai donc créé une autre version, qui génère des nombres sur une échelle logarithmique:
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);
}
Quelques tests:
Voici les résultats triés de la génération de 10 000 nombres doubles aléatoires entre 0 et Double.MaxValue
avec les deux stratégies. Les résultats sont affichés avec une échelle logarithmique:
Bien que les valeurs aléatoires linéaires semblent erronées à première vue, les statistiques montrent qu'aucune d'elles n'est "meilleure" que l'autre: même la stratégie linéaire a une distribution égale et la différence moyenne entre les valeurs est à peu près la même avec les deux stratégies .
Jouer avec différentes plages m'a montré que la stratégie linéaire devient "saine" avec une plage comprise entre 0 et ushort.MaxValue
une valeur minimum "raisonnable" de 10,78294704 (pour une ulong
plage, la valeur minimum était 3,03518E + 15 int
;: 353341). Ce sont les mêmes résultats des deux stratégies affichées avec des échelles différentes:
Éditer:
Récemment, j'ai rendu mes bibliothèques open source, n'hésitez pas à voir la RandomExtensions.NextDouble
méthode avec la validation complète.