Я немного опоздал на вечеринку, но мне нужно было реализовать общее решение, и оказалось, что ни одно из решений не может удовлетворить мои потребности.
Принятое решение хорошо для небольших диапазонов; однако, maximum - minimum
может быть бесконечность для больших диапазонов. Таким образом, исправленная версия может быть этой версией:
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));
}
Это генерирует случайные числа хорошо даже между double.MinValue
и double.MaxValue
. Но это создает еще одну «проблему», которая хорошо представлена в этом посте : если мы используем такие большие диапазоны, значения могут показаться слишком «неестественными». Например, после генерации 10000 случайных двойных double.MaxValue
значений между 0 и все значения были между 2,9579E + 304 и 1,7976E + 308.
Поэтому я создал еще одну версию, которая генерирует числа в логарифмическом масштабе:
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);
}
Некоторые тесты:
Вот отсортированные результаты генерации 10000 случайных двойных чисел между 0 и Double.MaxValue
обеими стратегиями. Результаты отображаются с использованием логарифмической шкалы:
Хотя линейные случайные значения на первый взгляд кажутся неправильными, статистика показывает, что ни одно из них не «лучше», чем другое: даже линейная стратегия имеет равномерное распределение, и средняя разница между значениями практически одинакова для обеих стратегий. ,
Игра с разными диапазонами показала мне, что линейная стратегия становится «вменяемой» с диапазоном от 0 до ushort.MaxValue
«разумного» минимального значения 10,78294704 (для ulong
диапазона минимальное значение было 3,03518E + 15 int
;: 353341). Это одинаковые результаты обеих стратегий, отображаемые в разных масштабах:
Редактировать:
Недавно я сделал мои библиотеки с открытым исходным кодом, не стесняйтесь увидеть RandomExtensions.NextDouble
метод с полной проверкой.