2010-07-29 2 views
40

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

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

+20

4.0 - случайный выбранный честным броском кубиков с плавающей точкой. –

+5

@ JesseC.Slicer Вы забыли указать ссылку: http://xkcd.com/221/ – jpmc26

ответ

48

Лучший подход, без сумасшедших значений, distributed with respect to the representable intervals on the floating-point number line (удален «униформа», как по отношению к непрерывной числовой прямой это явно неоднородна):

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

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

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 Software Developer's Manual Volume 1: Basic Architecture. Y-ось логарифмическая (основание-2), так как последовательные числа двоичных чисел с плавающей точкой делать линейно не отличаются.

Comparison of distributions, logarithmic Y-axis

+0

Не существует метода BitConverter.GetSingle. Вы имеете в виду ToSingle? – KrisTrip

+0

Это было бы то, что я имею в виду ... – user7116

+1

Использование случайных байтов легко может привести к значению NaN, и распределение может быть неравномерным. (Я не хотел бы прогнозировать распределение.) –

20

Любые причины не использовать 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, денормальные числа, очень большие числа, ноль и т. Д.

+0

Я думаю, что NextDouble просто предоставляет значения от 0.0 до 1.0, и я хочу больше диапазона, чем это (например, от float.MinValue до float.MaxValue). Думаю, я должен был указать :) – KrisTrip

+3

Просто умножьте его на (MaxValue-MinValue) и добавьте MinValue к нему? Итак, что-то вроде Random.NextDouble * (float.MaxValue-float.MinValue) + float.MinValue – Xzhsh

+0

Распределение создаваемых значений является бимодальным, я полагаю, что он имеет отношение к тому, насколько велики значения в диапазоне. – user7116

1

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

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.

0

Другим решением является сделать это:

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

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

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 
+1

Вы уверены, что это будет быстрее, потому что его сила в два? Я бы предположил, что это будет неявно применено к float * (который не будет точно представлять значение, а затем использовать регулярное разделение float). * – ideasman42

+0

Спасибо, что заметили, что это был хороший улов! Вы правы, что сначала он был преобразован в float, поэтому компилятор не мог использовать инструкцию сдвига. Я удалил «быстрее, потому что это сила двух». Как вы можете сказать, я сделал несколько других изменений, включая добавление новой функции, а также выполнение тестов. – Sunsetquest

0

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

Я предпочитаю использовать следующий код для генерации десятичного числа до кулака десятичной точки. вы можете скопировать вставку третьей строки, чтобы добавить число после десятичной точки, добавив это число в строку «в сочетании». Вы можете установить минимальное и максимальное значение, изменив 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); 

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

Смежные вопросы