Pergunta

Como posso obter um System.Decimal aleatório? System.Random não apoiá-lo diretamente.

Foi útil?

Solução

EDIT: Removido versão antiga

Este é semelhante à versão de Daniel, mas vai dar a gama completa. Além disso, introduz um novo método de extensão para obter um aleatório "qualquer inteiro" valor, o que eu acho que é muito útil.

Note que a distribuição de casas decimais aqui não é uniforme .

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

Outras dicas

Você poderia esperar de um-número gerador aleatório que não só gerou números aleatórios, mas que os números foram uniformemente gerado aleatoriamente.

Existem duas definições de uniformemente aleatória: discreto uniformemente aleatória e < a href = "http://en.wikipedia.org/wiki/Uniform_distribution_(continuous)" rel = "noreferrer nofollow"> contínua uniformemente aleatória.

marcas Discretamente uniformemente aleatórios sentido para um gerador de números aleatórios que tem um número finito de diferentes resultados possíveis. Por exemplo gerando um inteiro entre 1 e 10. Você, então, esperar que a probabilidade de obter 4 é o mesmo que pegar 7.

continuamente uniformemente marcas aleatórias sentir quando o gerador de número aleatório gera números em um intervalo. Por exemplo, um gerador que gera um número real entre 0 e 1. Você, então, esperar que a probabilidade de obter um número entre 0 e 0,5 é o mesmo que pegar um número entre 0,5 e 1.

Quando um gerador de números aleatórios gera números de ponto flutuante (que é basicamente o que um System.Decimal é - é apenas de ponto flutuante que base 10), pode-se argumentar que a definição adequada de uniformemente aleatória é:

Por um lado, uma vez que o número de ponto flutuante está sendo representado por um número fixo de bits em um computador, é óbvio que há um número finito de resultados possíveis. Assim, pode-se argumentar que a distribuição adequada é uma distribuição contínua discreta com cada número representável tendo a mesma probabilidade. Isso é basicamente o que e do Jon Skeet John Leidegren faz.

Por outro lado, pode-se argumentar que uma vez que um número de ponto flutuante é suposto ser uma aproximação para um número real, que seria melhor, tentando aproximar o comportamento de um gerador de números aleatórios contínua - embora são RNG real é realmente discreta. Esse é o comportamento que você começa de Random.NextDouble (), onde - apesar de existirem cerca de tantos números representáveis ??na faixa 0,00001-,00002 como não estão na faixa de 0,8-0,9, você é mil vezes mais propensos a ter uma número no segundo intervalo -. como seria de esperar

Assim, uma aplicação adequada de um Random.NextDecimal () provavelmente deve ser continuamente distribuído uniformemente.

Aqui é uma simples variação da resposta de Jon Skeet que é distribuído uniformemente entre 0 e 1 (I reutilizar seu) método de extensão NextInt32 ():

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

Você também pode discutir como obter uma distribuição uniforme em toda a faixa de decimais. Há provavelmente uma maneira mais fácil de fazer isso, mas esta ligeira modificação deve produzir uma distribuição relativamente uniforme:

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

Basicamente, nós certifique-se de que os valores de escala são escolhidos de forma proporcional ao tamanho do intervalo correspondente.

Isso significa que devemos obter uma escala de 0 a 90% do tempo - desde que faixa contém 90% da gama possível -. Escala de 1 9% do tempo, etc

Existem ainda alguns problemas com a implementação, uma vez que leva em conta que alguns números têm múltiplas representações -., Mas deve ser muito mais perto de uma distribuição uniforme do que as outras implementações

Aqui está Decimal aleatório com a implementação Range que funciona bem para mim.

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

Eu sei que isto é uma questão de idade, mas a questão de distribuição Rasmus Faber descrito continuou me incomodando, então eu vim com Os seguintes. Eu não olhei em profundidade na implementação NextInt32 fornecido por Jon Skeet e estou supondo (esperando) que tem a mesma distribuição que 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);
}

É também, através do poder do material fácil, fazer:

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

Eu intrigado com isso por um pouco. Este é o melhor que eu poderia vir acima com:

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: Como observado nos comentários lo, médio e oi nunca pode conter int.MaxValue de modo a gama completa de decimais não é possível

.

aqui vai ... usa a biblioteca cripta para gerar um par de bytes aleatórios, então convertes-los para um valor decimal ... veja MSDN para o decimal construtor

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

revisto para usar um construtor decimal diferente para dar uma melhor gama de números

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

Confira no link a seguir para implementações prontas que deve ajudar:

MathNet.Numerics, números aleatórios e Distribuições de Probabilidade

As extensas distribuições são especialmente de interesse, construído no topo dos geradores de números aleatórios (MersenneTwister, etc.) directamente a partir de derivados System.Random, todos proporcionando úteis os métodos de extensão (por exemplo NextFullRangeInt32, NextFullRangeInt64, NextDecimal, etc). Você pode, é claro, é só usar o SystemRandomSource padrão, que é simplesmente System.Random embelezado com os métodos de extensão.

Oh, e você pode criar suas instâncias RNG como thread-safe se você precisar dele.

Muito útil na verdade!

Esta é uma questão de idade, mas para aqueles que estão apenas lê-lo, por que re-inventar a roda?

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

Para ser honesto eu não acredito que o formato interno do C # decimal funciona da maneira que muitas pessoas pensam. Por esta razão, pelo menos, algumas das soluções apresentadas aqui são possivelmente inválida ou não pode trabalhar de forma consistente. Considere os 2 números seguintes e como eles são armazenados no formato decimal:

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

e

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

Tome nota especial de como a escala é diferente, mas ambos os valores são quase a mesma, isto é, ambos são menos de 1 por apenas uma pequena fração. Parece que é a escala e o número de dígitos que têm uma relação direta. A menos que eu estou faltando alguma coisa, isso deve jogar uma chave de macaco em mais qualquer código que mexe com a parte inteira de 96 bits de um decimal, mas deixa a escala inalterados.

Em experiências descobri que o 0.9999999999999999999999999999m número, que tem 28 noves, tem o número máximo de noves possíveis antes do decimal vai arredondar para 1,0 m.

Além disso experimentando provou o seguinte código define a variável "Dezembro" para o valor 0.9999999999999999999999999999m:

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

É a partir dessa descoberta que eu vim com as extensões para a classe aleatório que pode ser visto no código abaixo. Eu acredito que este código é totalmente funcional e em boas condições de funcionamento, mas ficaria feliz para outros olhos para estar verificando-lo para erros. Eu não sou um estatístico, então não posso dizer se este código produz uma distribuição verdadeiramente uniforme de decimais, mas se eu tivesse que adivinhar, eu diria que não a perfeição, mas vem muito próxima (como em 1 chamada de 51 trillion favorecendo um determinado intervalo de números).

A função primeiro NextDecimal () deve produzir valores iguais ou maiores do que 0.0m e menos do que 1,0 m. A / while impede declaração RandH e Randl exceda o valor 0.99999999999999d por looping até que eles estão abaixo desse valor. Eu acredito que as chances de esse loop sempre repetindo é de 1 em 51 trilhões (ênfase na palavra acreditar, eu não confio em minha matemática). Este, por sua vez, deve impedir que as funções de nunca arredondando o valor de retorno até 1,0 m.

A segunda função NextDecimal () deve funcionar o mesmo que a função Random.Next (), apenas com valores decimais em vez de números inteiros. Na verdade, não têm vindo a utilizar esta segunda função NextDecimal () e não tenha testado. Sua bastante simples, então eu acho que tenho direito, mas novamente, eu não testei ele -. Assim que você vai querer se certificar de que está funcionando corretamente antes de confiar nele

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

Eu queria gerar decimais "aleatórios" até 9 casas decimais. Minha abordagem foi apenas para gerar um duplo e dividi-lo para as casas decimais.

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

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

o "randomInt" é o número antes da casa decimal, você poderia apenas colocar 0. Para reduzir os pontos decimais simplesmente remover "9" é de forma aleatória e "0" s na divisória

Uma vez que a questão OP é muito abrangente e quer apenas um System.Decimal aleatória, sem qualquer restrição, a seguir é uma solução muito simples que funcionou para mim.

Eu não estava preocupado com qualquer tipo de uniformidade ou a precisão dos números gerados, para que outros respostas aqui são provavelmente melhor se você tem algumas restrições, mas este funciona bem em casos simples.

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

No meu caso específico, eu estava procurando um decimal aleatório para uso como uma cadeia de dinheiro, por isso a minha solução completa foi:

string value;
value = val = Math.Round(new decimal(rnd.NextDouble()) * 1000,2).ToString("0.00", System.Globalization.CultureInfo.InvariantCulture);
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top