Генерация случайного десятичного числа в C#


Как я могу получить случайную систему.Десятичное число? System.Random не поддерживает его напрямую.

12 54

12 ответов:

изменить: удалена старая версия

Это похоже на версию Даниила, но даст полный диапазон. Он также вводит новый метод расширения, чтобы получить случайное "любое целое" значение, которое я думаю, удобно.

обратите внимание, что распределение десятичных знаков здесь не однородна.

/// <summary>
/// Returns an Int32 with a random value across the entire range of
/// possible values.
/// </summary>
public static int NextInt32(this Random rng)
{
     int firstBits = rng.Next(0, 1 << 4) << 28;
     int lastBits = rng.Next(0, 1 << 28);
     return firstBits | lastBits;
}

public static decimal NextDecimal(this Random rng)
{
     byte scale = (byte) rng.Next(29);
     bool sign = rng.Next(2) == 1;
     return new decimal(rng.NextInt32(), 
                        rng.NextInt32(),
                        rng.NextInt32(),
                        sign,
                        scale);
}

есть два определения равномерно случайных: дискретные случайные равномерно и непрерывный равномерно случайный.

дискретно равномерно случайный имеет смысл для генератора случайных чисел, который имеет конечное число различных возможных результатов. Например, создание целого числа между 1 и 10. Тогда вы ожидаете, что вероятность получения 4 такая же, как и получение 7.

непрерывно, однородно случайный смысл, когда генератор случайных чисел генерирует числа в диапазоне. Например, генератор, который генерирует вещественное число между 0 и 1. Тогда вы ожидаете, что вероятность получения числа от 0 до 0,5 будет такой же, как и получение числа от 0,5 до 1.

когда генератор случайных чисел генерирует числа с плавающей точкой числа (что в основном то, что система.Decimal is-это просто плавающая точка, которая является основанием 10), можно утверждать, что правильное определение равномерно случайного:

Джон Скит и Джон Leidegren это!--6--> реализации.

Так что правильная реализация случайного.NextDecimal (), вероятно, должен быть непрерывно равномерно распределен.

вот простой вариант ответа Джона Скита, который равномерно распределен между 0 и 1 (я повторно использую его расширение NextInt32 метод):

public static decimal NextDecimal(this Random rng)
{
     return new decimal(rng.NextInt32(), 
                        rng.NextInt32(),
                        rng.Next(0x204FCE5E),
                        false,
                        0);
}

вы также могли бы обсудить, как получить равномерное распределение по всему диапазону разрядов. Есть наверное более простой способ сделать это, но это небольшая модификация ответ Джона Лейдегрена должно производить относительно равномерное распределение:

private static int GetDecimalScale(Random r)
{
  for(int i=0;i<=28;i++){
    if(r.NextDouble() >= 0.1)
      return i;
  }
  return 0;
}

public static decimal NextDecimal(this Random r)
{
    var s = GetDecimalScale(r);
    var a = (int)(uint.MaxValue * r.NextDouble());
    var b = (int)(uint.MaxValue * r.NextDouble());
    var c = (int)(uint.MaxValue * r.NextDouble());
    var n = r.NextDouble() >= 0.5;
    return new Decimal(a, b, c, n, s);
}

в принципе, мы следим за тем, чтобы значения масштаба выбирались пропорционально размеру соответствующего диапазона.

это означает, что мы должны получить шкалу 0 90% времени-поскольку этот диапазон содержит 90% от возможного диапазона - шкала 1 9% времени и т. д.

есть еще некоторые проблемы с реализацией, так как он учитывает, что некоторые числа имеют несколько представлений, но он должен быть намного ближе к равномерному распределению, чем другие реализации.

вот десятичный случайный с реализацией диапазона, который отлично работает для меня.

public static decimal NextDecimal(this Random rnd, decimal from, decimal to)
{
    byte fromScale = new System.Data.SqlTypes.SqlDecimal(from).Scale;
    byte toScale = new System.Data.SqlTypes.SqlDecimal(to).Scale;

    byte scale = (byte)(fromScale + toScale);
    if (scale > 28)
        scale = 28;

    decimal r = new decimal(rnd.Next(), rnd.Next(), rnd.Next(), false, scale);
    if (Math.Sign(from) == Math.Sign(to) || from == 0 || to == 0)
        return decimal.Remainder(r, to - from) + from;

    bool getFromNegativeRange = (double)from + rnd.NextDouble() * ((double)to - (double)from) < 0;
    return getFromNegativeRange ? decimal.Remainder(r, -from) + from : decimal.Remainder(r, to);
}

Я знаю, это старый вопрос, но проблема распространения Расмус Фабер описал продолжал беспокоить меня, поэтому я придумал следующее. Я не смотрел в глубину на NextInt32 реализация обеспечивается Джон Скит и я предполагаю (надеюсь), что он имеет такое же распределение, как случайные.Далее ().

//Provides a random decimal value in the range [0.0000000000000000000000000000, 0.9999999999999999999999999999) with (theoretical) uniform and discrete distribution.
public static decimal NextDecimalSample(this Random random)
{
    var sample = 1m;
    //After ~200 million tries this never took more than one attempt but it is possible to generate combinations of a, b, and c with the approach below resulting in a sample >= 1.
    while (sample >= 1)
    {
        var a = random.NextInt32();
        var b = random.NextInt32();
        //The high bits of 0.9999999999999999999999999999m are 542101086.
        var c = random.Next(542101087);
        sample = new Decimal(a, b, c, false, 28);
    }
    return sample;
}

public static decimal NextDecimal(this Random random)
{
    return NextDecimal(random, decimal.MaxValue);
}

public static decimal NextDecimal(this Random random, decimal maxValue)
{
    return NextDecimal(random, decimal.Zero, maxValue);
}

public static decimal NextDecimal(this Random random, decimal minValue, decimal maxValue)
{
    var nextDecimalSample = NextDecimalSample(random);
    return maxValue * nextDecimalSample + minValue * (1 - nextDecimalSample);
}

это также, через силу легких вещей, чтобы сделать:

var rand = new Random();
var item = new decimal(rand.NextDouble());

Я немного озадачился этим. Это лучшее, что я мог придумать:

public class DecimalRandom : Random
    {
        public override decimal NextDecimal()
        {
            //The low 32 bits of a 96-bit integer. 
            int lo = this.Next(int.MinValue, int.MaxValue);
            //The middle 32 bits of a 96-bit integer. 
            int mid = this.Next(int.MinValue, int.MaxValue);
            //The high 32 bits of a 96-bit integer. 
            int hi = this.Next(int.MinValue, int.MaxValue);
            //The sign of the number; 1 is negative, 0 is positive. 
            bool isNegative = (this.Next(2) == 0);
            //A power of 10 ranging from 0 to 28. 
            byte scale = Convert.ToByte(this.Next(29));

            Decimal randomDecimal = new Decimal(lo, mid, hi, isNegative, scale);

            return randomDecimal;
        }
    }

Edit: как отмечено в комментариях lo, mid и hi никогда не могут содержать int.MaxValue таким образом, полный диапазон десятичных знаков невозможен.

вот так... использует библиотеку crypt для генерации нескольких случайных байтов, а затем преобразует их в десятичное значение... смотрите MSDN для десятичного конструктора

using System.Security.Cryptography;

public static decimal Next(decimal max)
{
    // Create a int array to hold the random values.
    Byte[] randomNumber = new Byte[] { 0,0 };

    RNGCryptoServiceProvider Gen = new RNGCryptoServiceProvider();

    // Fill the array with a random value.
    Gen.GetBytes(randomNumber);

    // convert the bytes to a decimal
    return new decimal(new int[] 
    { 
               0,                   // not used, must be 0
               randomNumber[0] % 29,// must be between 0 and 28
               0,                   // not used, must be 0
               randomNumber[1] % 2  // sign --> 0 == positive, 1 == negative
    } ) % (max+1);
}

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

public static decimal Next(decimal max)
{
    // Create a int array to hold the random values.
    Byte[] bytes= new Byte[] { 0,0,0,0 };

    RNGCryptoServiceProvider Gen = new RNGCryptoServiceProvider();

    // Fill the array with a random value.
    Gen.GetBytes(bytes);
    bytes[3] %= 29; // this must be between 0 and 28 (inclusive)
    decimal d = new decimal( (int)bytes[0], (int)bytes[1], (int)bytes[2], false, bytes[3]);

        return d % (max+1);
    }

Проверьте следующую ссылку для готовых реализаций, которые должны помочь:

MathNet.Цифры, случайные числа и распределения вероятностей

особенно интересны обширные распределения, построенные поверх генераторов случайных чисел(MersenneTwister и др.) непосредственно из системы.Случайные, все обеспечивающие удобные методы расширения (например, NextFullRangeInt32, NextFullRangeInt64, NextDecimal и т. д.). Вы можете, конечно, просто использовать по умолчанию SystemRandomSource, который является просто системой.Случайные украшенные с методами расширения.

О, и вы можете создать свои экземпляры RNG как потокобезопасные, если вам это нужно.

очень удобно!

Это старый вопрос, но для тех, кто просто читал его, зачем заново изобретать колесо?

static decimal GetRandomDecimal()
    {

        int[] DataInts = new int[4];
        byte[] DataBytes = new byte[DataInts.Length * 4];

        // Use cryptographic random number generator to get 16 bytes random data
        RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();

        do
        {
            rng.GetBytes(DataBytes);

            // Convert 16 bytes into 4 ints
            for (int index = 0; index < DataInts.Length; index++)
            {
                DataInts[index] = BitConverter.ToInt32(DataBytes, index * 4);
            }

            // Mask out all bits except sign bit 31 and scale bits 16 to 20 (value 0-31)
            DataInts[3] = DataInts[3] & (unchecked((int)2147483648u | 2031616));

          // Start over if scale > 28 to avoid bias 
        } while (((DataInts[3] & 1835008) == 1835008) && ((DataInts[3] & 196608) != 0));

        return new decimal(DataInts);
    }
    //end

честно говоря, я не верю, что внутренний формат c# decimal работает так, как думают многие люди. По этой причине, по крайней мере, некоторые из представленных здесь решений, возможно, недействительны или не могут работать последовательно. Рассмотрим следующие 2 числа и то, как они хранятся в десятичном формате:

0.999999999999999m
Sign: 00
96-bit integer: 00 00 00 00 FF 7F C6 A4 7E 8D 03 00
Scale: 0F

и

0.9999999999999999999999999999m
Sign: 00
96-bit integer: 5E CE 4F 20 FF FF FF 0F 61 02 25 3E
Scale: 1C

обратите особое внимание на то, как масштаб отличается, но оба значения почти одинаковы, то есть они оба меньше 1 только на крошечный дробь. Похоже, что это масштаб и количество знаков, которые имеют прямое отношение. Если я ничего не упускаю, это должно бросить гаечный ключ в большинство любого кода, который вмешивается в 96-битную целочисленную часть десятичного числа, но оставляет масштаб неизменным.

в экспериментах я обнаружил, что число 0.99999999999999999999999999 м, которое имеет 28 девятки, имеет максимальное количество девятки возможно до десятичного округления до 1.0 м.

далее эксперименты доказали следующий код присваивает переменной "декабря" на 0.9999999999999999999999999999 значение M:

double DblH = 0.99999999999999d;
double DblL = 0.99999999999999d;
decimal Dec = (decimal)DblH + (decimal)DblL / 1E14m;

именно из этого открытия я придумал расширения для случайного класса, которые можно увидеть в коде ниже. Я считаю, что этот код полностью функциональный и в хорошем рабочем состоянии, но был бы рад, чтобы другие глаза проверяли его на ошибки. Я не статистик, поэтому я не могу сказать, производит ли этот код действительно равномерное распределение десятичных знаков, но если я пришлось угадывать, я бы сказал, что это не совершенство, но очень близко (как в 1 из 51 трлн пользу определенного диапазона чисел).

первая функция NextDecimal () должна выдавать значения, равные или превышающие 0,0 м и менее 1,0 м. оператор do/while предотвращает превышение RandH и RandL значения 0,99999999999999 d путем циклирования, пока они не будут ниже этого значения. Я считаю, что шансы на повторение этого цикла составляют 1 из 51 триллиона (акцент на слове поверьте, я не доверяю своей математике). Это, в свою очередь, должно препятствовать тому, чтобы функции когда-либо округляли возвращаемое значение до 1,0 м.

вторая функция NextDecimal () должна работать так же, как и случайная.Функция Next (), только с десятичными значениями вместо целых чисел. На самом деле я не использовал эту вторую функцию NextDecimal() и не тестировал ее. Его довольно просто, поэтому я думаю, что у меня все правильно, но опять же, я не проверял его - так что вы хотите, чтобы убедиться, что он работает правильно прежде чем полагаться на него.

public static class ExtensionMethods {
    public static decimal NextDecimal(this Random rng) {
        double RandH, RandL;
        do {
            RandH = rng.NextDouble();
            RandL = rng.NextDouble();
        } while((RandH > 0.99999999999999d) || (RandL > 0.99999999999999d));
        return (decimal)RandH + (decimal)RandL / 1E14m;
    }
    public static decimal NextDecimal(this Random rng, decimal minValue, decimal maxValue) {
        return rng.NextDecimal() * (maxValue - minValue) + minValue;
    }
}

Я хотел создать "случайные" десятичные знаки до 9 десятичных знаков. Мой подход состоял в том, чтобы просто создать двойник и разделить его на десятичные числа.

int randomInt = rnd.Next(0, 100);

double randomDouble = rnd.Next(0, 999999999);
decimal randomDec = Convert.ToDecimal(randomint) + Convert.ToDecimal((randomDouble/1000000000));

"randomInt" - это число перед десятичным знаком, вы можете просто поставить 0. Чтобы уменьшить десятичные точки, просто удалите "9" S в случайном порядке и"0" s в делении

Так как вопрос OP очень обнимает и просто хочет случайную систему.Десятичный без каких-либо ограничений, ниже очень простое решение, которое сработало для меня.

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

Random rnd = new Random();
decimal val;
int decimal_places = 2;
val = Math.Round(new decimal(rnd.NextDouble()), decimal_places);

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

string value;
value = val = Math.Round(new decimal(rnd.NextDouble()) * 1000,2).ToString("0.00", System.Globalization.CultureInfo.InvariantCulture);