Llegué un poco tarde a la fiesta, pero necesitaba implementar una solución general y resultó que ninguna de las soluciones puede satisfacer mis necesidades.
La solución aceptada es buena para rangos pequeños; sin embargo, maximum - minimum
puede ser infinito para grandes rangos. Entonces una versión corregida puede ser esta versión:
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));
}
Esto genera números aleatorios muy bien incluso entre double.MinValue
y double.MaxValue
. Pero esto introduce otro "problema", que se presenta muy bien en esta publicación : si usamos rangos tan grandes, los valores pueden parecer demasiado "antinaturales". Por ejemplo, después de generar 10,000 dobles al azar entre 0 y double.MaxValue
todos los valores estaban entre 2.9579E + 304 y 1.7976E + 308.
Así que creé también otra versión, que genera números en una escala logarítmica:
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);
}
Algunas pruebas:
Aquí están los resultados ordenados de generar 10,000 números dobles aleatorios entre 0 y Double.MaxValue
con ambas estrategias. Los resultados se muestran usando la escala logarítmica:
Aunque los valores aleatorios lineales parecen estar equivocados a primera vista, las estadísticas muestran que ninguno de ellos es "mejor" que el otro: incluso la estrategia lineal tiene una distribución uniforme y la diferencia promedio entre los valores es prácticamente la misma con ambas estrategias. .
Jugar con diferentes rangos me mostró que la estrategia lineal llega a ser "sana" con un rango entre 0 y ushort.MaxValue
con un valor mínimo "razonable" de 10.78294704 (para el ulong
rango, el valor mínimo era 3.03518E + 15 int
;: 353341). Estos son los mismos resultados de ambas estrategias que se muestran con diferentes escalas:
Editar:
Recientemente hice mis bibliotecas de código abierto, siéntase libre de ver el RandomExtensions.NextDouble
método con la validación completa.