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

StackOverflow https://stackoverflow.com/questions/609501

  •  03-07-2019
  •  | 
  •  

Вопрос

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

Это было полезно?

Решение

РЕДАКТИРОВАТЬ: Удалена старая версия

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

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

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

Другие советы

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

Существует два определения равномерно случайных: дискретно равномерно случайных и < a href = "http://en.wikipedia.org/wiki/Uniform_distribution_(continuous)" rel = "nofollow noreferrer"> непрерывно равномерно случайный .

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

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

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

С одной стороны, поскольку число с плавающей запятой представляется в компьютере фиксированным числом битов, очевидно, что существует конечное число возможных результатов. Таким образом, можно утверждать, что правильное распределение - это дискретное непрерывное распределение, в котором каждое представимое число имеет одинаковую вероятность. Это в основном то, что Джон Скит и Реализация Джона Лейдегрена делает.

С другой стороны, можно утверждать, что, поскольку число с плавающей запятой, как предполагается, является приближением к действительному числу, было бы лучше попытаться приблизить поведение непрерывного генератора случайных чисел - даже если Фактический ГСЧ фактически дискретен. Это поведение, которое вы получаете от Random.NextDouble (), где - даже если в диапазоне 0,00001-0,00002 представлено примерно столько представимых чисел, сколько в диапазоне 0,8-0,9, у вас в тысячу раз больше шансов получить число во втором диапазоне - как и следовало ожидать.

Таким образом, правильная реализация Random.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% времени и т. д.

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

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

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, предоставленную Джоном Скитом , и я предполагаю (надеюсь), что он имеет такое же распределение, что и Random.Next () .

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

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

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

Да, и вы можете создавать свои экземпляры 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.999999999999999999999999999999 m, которое имеет 28 девяток, имеет максимально возможное количество девяток до того, как десятичная дробь округлится до 1.0m.

Дальнейшие эксперименты показали, что следующий код устанавливает переменную "Dec" в значение 0.99999999999999999999999999999999m:

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

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

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

Вторая функция NextDecimal() должна работать так же, как функция Random.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));

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

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

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

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);
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top