Создание случайного десятичного числа в С#

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

Ответ 1

EDIT: удалена старая версия

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

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

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

Ответ 2

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

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

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

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

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

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

Джона Лейдегрена.

С другой стороны, можно утверждать, что, поскольку число с плавающей запятой должно быть приближением к действительному числу, нам было бы лучше, если бы мы попытались аппроксимировать поведение непрерывного генератора случайных чисел, даже если они фактический RNG на самом деле дискретный. Это поведение, которое вы получаете от 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);
}

Вы также можете обсудить, как получить равномерное распределение по всему диапазону десятичных знаков. Вероятно, есть более простой способ сделать это, но эта небольшая модификация ответа John Leidegren должна давать относительно равномерное распределение:

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% времени и т.д.

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

Ответ 3

Вот десятичная случайность с реализацией 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);
}

Ответ 4

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

Ответ 5

Кроме того, благодаря силе простых вещей:

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

Ответ 6

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

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, поэтому полный диапазон Decimals невозможен.

Ответ 7

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

Ответ 8

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

Ответ 9

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

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

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

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

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

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

Ответ 10

Честно говоря, я не считаю, что внутренний формат десятичного числа С# работает так, как думают многие. По этой причине хотя бы некоторые из представленных здесь решений могут быть недействительными или могут не работать последовательно. Рассмотрим следующие 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.999999999999999999999999999999m, которое имеет 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 путем циклизации до тех пор, пока они не станут ниже этого значения. Я считаю, что шансы этой петли повторяются в 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;
    }
}