Лучший способ генерации случайного поплавка в С#

Каков наилучший способ генерации случайного поплавка в С#?

Обновление: мне нужны случайные числа с плавающей запятой из float.Minvalue для float.Maxvalue. Я использую эти числа в модульном тестировании некоторых математических методов.

Ответ 1

Наилучший подход, без сумасшедших значений распределенный по представимым интервалам в числовой строке с плавающей запятой (удаляется "равномерным" по отношению к непрерывному числу строка явно неравномерна):

static float NextFloat(Random random)
{
    double mantissa = (random.NextDouble() * 2.0) - 1.0;
    double exponent = Math.Pow(2.0, random.Next(-126, 128));
    return (float)(mantissa * exponent);
}

Другой подход, который даст вам некоторые безумные значения (равномерное распределение битовых шаблонов), потенциально полезное для fuzzing:

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

Наименее полезный подход:

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

Число строк с плавающей точкой: Руководство разработчика программного обеспечения Intel Architecture Volume 1: Базовая архитектура. Ось Y является логарифмической (базовая 2), поскольку последовательные двоичные числа с плавающей запятой не отличаются линейно.

Comparison of distributions, logarithmic Y-axis

Ответ 2

Любая причина не использовать Random.NextDouble, а затем использовать для float? Это даст вам поплавок между 0 и 1.

Если вам нужна другая форма "лучшего", вам нужно будет указать свои требования. Обратите внимание, что Random не следует использовать для таких важных вопросов, как финансы или безопасность, - и вы должны, как правило, повторно использовать существующий экземпляр во всей своей программе или один для каждого потока (поскольку Random не является потокобезопасным).

EDIT: как предложено в комментариях, чтобы преобразовать это в диапазон float.MinValue, 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: теперь вы упомянули, что это для модульного тестирования, я не уверен, что это идеальный подход. Вероятно, вам следует проверить конкретные значения - убедитесь, что вы тестируете образцы в каждой из соответствующих категорий - бесконечности, NaNs, денормальные числа, очень большие числа, ноль и т.д.

Ответ 3

Еще одна версия... (я думаю, что это очень хорошо)

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

Я думаю, что это...

  • является вторым наиболее быстрым (см. тесты)
  • равномерно распределен

И еще одна версия... (не так хорошо, но публикуется в любом случае)

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

Я думаю, что это...

  • является самым быстрым (см. тесты)
  • равномерно распределяется, так как Next() является случайным значением 31 бит, он возвращает только значения 2 ^ 31. (50% соседних значений будут иметь одинаковое значение)

Тестирование большинства функций на этой странице: (i7, выпуск, без отладки, 2 ^ 28 циклов)

 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

Ответ 4

Я принял несколько иной подход, чем другие

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

Комментарии объясняют, что я делаю. Получите следующий double, преобразуйте это число в значение от -1 до 1, а затем умножьте его на float.MaxValue.

Ответ 5

Другим решением является следующее:

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

Ответ 6

Вот еще один способ, которым я придумал: Скажем, вы хотите получить поплавок между 5.5 и 7, с 3 десятичными знаками.

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

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

Таким образом, вы всегда получите большее число, чем 5.5, и меньшее число, чем 7.