Генерация случайного десятичного числа в C # - PullRequest
59 голосов
/ 04 марта 2009

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

Ответы [ 12 ]

42 голосов
/ 04 марта 2009

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

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

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

/// <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);
}
9 голосов
/ 04 марта 2009

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

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

Дискретно равномерно случайный имеет смысл для генератора случайных чисел, который имеет конечное число различных возможных результатов. Например, генерация целого числа от 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% времени и т. Д.

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

6 голосов
/ 12 сентября 2012

Вот десятичное случайное число с реализацией 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);
}
5 голосов
/ 04 марта 2015

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

С помощью простых вещей также можно:

var rand = new Random();
var item = new decimal(rand.NextDouble());
2 голосов
/ 04 марта 2009

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

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

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

1 голос
/ 22 ноября 2015

Если честно, я не верю, что внутренний формат десятичного числа C # работает так, как думают многие. По этой причине, по крайней мере, некоторые из представленных здесь решений могут быть недействительными или работать некорректно. Рассмотрим следующие 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.9999999999999999999999999999m, которое имеет 28 девяток, имеет максимально возможное число девяток до округления десятичной дроби до 1,0 м.

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

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

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

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

Вторая функция 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;
    }
}
1 голос
/ 05 февраля 2014

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

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

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

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

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

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

1 голос
/ 27 августа 2009
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
1 голос
/ 04 марта 2009

здесь вы идете ... использует библиотеку 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);
    }
...