如何获得随机的System.Decimal? System.Random 不直接支持它。

有帮助吗?

解决方案

编辑:删除旧版本

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

其他提示

您通常会从随机数生成器中得到它不仅生成随机数,而且数字是均匀随机生成的。

统一随机有两种定义:离散均匀随机和< 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的浮点数),可以说是均匀随机的正确定义是什么:

一方面,由于浮点数由计算机中的固定位数表示,显然存在有限数量的可能结果。因此可以认为正确的分布是离散的连续分布,每个可表示的数字具有相同的概率。这基本上是 Jon Skeet's 约翰莱德格伦的实施确实如此。

另一方面,有人可能会争辩说,由于浮点数应该是实数的近似值,因此我们会通过尝试逼近连续随机数生成器的行为来获得更好的效果 - 即使是实际的RNG实际上是离散的。这是你从Random.NextDouble()获得的行为,其中 - 即使在0.00001-0.00002范围内有大约同样多的可表示数字,因为在0.8-0.9范围内,你获得一个数字的可能性要高出一千倍第二个范围内的数字 - 正如您所期望的那样。

因此,Random.NextDecimal()的正确实现应该可以连续均匀分布。

以下是Jon Skeet的答案的简单变体,它在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);
}

基本上,我们确保按照相应范围的大小按比例选择比例值。

这意味着我们应该在90%的时间内获得0的比例 - 因为该范围包含90%的可能范围 - 比例为19%的时间等等。

实现仍然存在一些问题,因为它确实考虑到一些数字有多个表示 - 但它应该比其他实现更接近统一分布。

这是Decimal随机的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);
}

我知道这是一个老问题,但是Rasmus Faber描述的发行问题一直困扰着我,所以我想出了下列。我没有深入研究Jon Skeet提供的 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;
        }
    }

编辑:正如评论中所指出的那样,mid和hi永远不能包含int.MaxValue,因此无法完整的Decimal范围。

这里你去...使用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,Random Numbers and Probability Distributions

广泛的分布特别值得关注,建立在直接从System.Random派生的随机数生成器(MersenneTwister等)之上,所有这些都提供了方便的扩展方法(例如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#小数的内部格式与许多人的想法一致。出于这个原因,这里提出的至少一些解决方案可能无效或可能无法一致地工作。考虑以下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个9,在小数点前最多可以达到最大数量为1.0m。

进一步的实验证明,以下代码设置变量“Dec”。值为0.9999999999999999999999999999m:

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

正是从这个发现中我想出了Random类的扩展,可以在下面的代码中看到。我相信这段代码功能齐全,处于良好的工作状态,但很高兴其他人可以检查错误。我不是统计学家,所以我不能说这段代码是否能产生真正均匀的小数分布,但是如果我不得不猜测我会说它完美但是非常接近(如51个万亿中的1个号召一定数量的数字)。

第一个NextDecimal()函数应该产生等于或大于0.0m且小于1.0m的值。 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;
    }
}

我想生成“随机”最多小数点后9位的小数。我的方法是生成一个double并将其除以小数。

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

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

“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