it-roy-ru.com

Лучший способ создать случайное число с плавающей точкой в ​​C #

Каков наилучший способ генерации случайного числа с плавающей точкой в ​​C #?

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

48
KrisTrip

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

static float NextFloat(Random random)
{
    double mantissa = (random.NextDouble() * 2.0) - 1.0;
    // choose -149 instead of -126 to also generate subnormal floats (*)
    double exponent = Math.Pow(2.0, random.Next(-126, 128));
    return (float)(mantissa * exponent);
}

(*) ... проверьте здесь для ненормальных поплавков

Предупреждение: генерирует положительную бесконечность! Выберите показатель 127, чтобы быть на безопасной стороне.

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

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

Улучшение по сравнению с предыдущей версией заключается в том, что он не создает «сумасшедшие» значения (ни бесконечности, ни NaN) и все еще быстр (также распределен по представимым интервалам в числовой строке с плавающей точкой):

public static float Generate(Random prng)
{
    var sign = prng.Next(2);
    var exponent = prng.Next((1 << 8) - 1); // do not generate 0xFF (infinities and NaN)
    var mantissa = prng.Next(1 << 23);

    var bits = (sign << 31) + (exponent << 23) + mantissa;
    return IntBitsToFloat(bits);
}

private static float IntBitsToFloat(int bits)
{
    unsafe
    {
        return *(float*) &bits;
    }
}

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

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 Том 1: Базовая архитектура. Ось Y является логарифмической (основание-2), потому что последовательные двоичные числа с плавающей запятой не отличаются линейно. 

Comparison of distributions, logarithmic Y-axis

59
user7116

Есть ли причина не использовать Random.NextDouble и затем приводить к float? Это даст вам плавание от 0 до 1.

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

Правка: Как предлагается в комментариях, чтобы преобразовать это в диапазон 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;

Правка: Теперь вы упомянули, что это для модульного тестирования, я не уверен, что это идеальный подход. Вы, вероятно, должны вместо этого тестировать с конкретными значениями - убедитесь, что вы тестируете с выборками в каждой из соответствующих категорий - бесконечности, NaN, ненормальные числа, очень большие числа, ноль и т.д.

24
Jon Skeet

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

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

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

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

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

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
5
Sunsetquest

Я взял немного другой подход, чем другие 

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

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

1
Anthony Pegram

Вот еще один способ, которым я придумал: Допустим, вы хотите получить число от 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.

0
Qedized

Я предпочитаю использовать следующий код для генерации десятичного числа вплоть до первой десятичной точки. Вы можете скопировать и вставить 3-ю строку, чтобы добавить больше чисел после десятичной точки, добавив это число в строку «комбинированные». Вы можете установить минимальное и максимальное значение, изменив 0 и 9 на ваше предпочтительное значение.

Random r = new Random();
string beforePoint = r.Next(0, 9).ToString();//number before decimal point
string afterPoint = r.Next(0,9).ToString();//1st decimal point
//string secondDP = r.Next(0, 9).ToString();//2nd decimal point
string combined = beforePoint+"."+afterPoint;
decimalNumber= float.Parse(combined);
Console.WriteLine(decimalNumber);

Я надеюсь, что это помогло вам.

0
Waheed Sattar

Другое решение состоит в том, чтобы сделать это:

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;
}
0
Simon Mourier