69

我怎样才能得到一个随机的 System.Decimal?System.Random不直接支持。

4

13 回答 13

55

编辑:删除旧版本

这类似于 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);
}
于 2009-03-04T07:12:23.413 回答
10

您通常会期望随机数生成器不仅生成随机数,而且这些数字是均匀随机生成的。

均匀随机有两种定义:离散均匀随机连续均匀随机

对于具有有限数量的不同可能结果的随机数生成器,离散均匀随机是有意义的。例如生成一个介于 1 和 10 之间的整数。然后您会期望得到 4 的概率与得到 7 的概率相同。

当随机数生成器生成一个范围内的数字时,连续均匀随机是有意义的。例如,生成器生成 0 到 1 之间的实数。然后您会期望得到 0 到 0.5 之间的数字的概率与得到 0.5 到 1 之间的数字的概率相同。

当随机数生成器生成浮点数(这基本上是 System.Decimal - 它只是以 10 为底的浮点数)时,统一随机的正确定义是有争议的:

一方面,由于浮点数在计算机中由固定数量的位表示,显然存在有限数量的可能结果。因此有人可能会争辩说,正确的分布是离散的连续分布,每个可表示的数字具有相同的概率。这基本上就是Jon SkeetJohn Leidegren 的实现所做的。

另一方面,有人可能会争辩说,由于浮点数应该是实数的近似值,我们最好尝试近似连续随机数生成器的行为——即使实际的 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);
}

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

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

实现仍然存在一些问题,因为它没有考虑到某些数字具有多种表示形式 - 但它应该比其他实现更接近均匀分布。

于 2009-03-04T11:50:53.773 回答
8

我知道这是一个老问题,但是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);
}
于 2015-03-04T17:15:41.543 回答
8

通过简单的东西的力量,它也可以做到:

var rand = new Random();
var item = new decimal(rand.NextDouble());
于 2017-04-06T14:52:53.050 回答
6

这是具有 Range 实现的 Decimal random 对我来说很好。

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);
}
于 2012-09-12T12:22:42.760 回答
2

我对此感到困惑。这是我能想到的最好的:

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,因此不可能包含完整的小数范围。

于 2009-03-04T07:03:40.093 回答
2

给你...使用 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);
    }
于 2009-03-04T08:34:35.960 回答
2

由于 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);
于 2018-01-23T18:12:17.373 回答
1
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
于 2009-08-27T16:39:11.220 回答
1

查看以下链接以获取应有帮助的现成实现:

MathNet.Numerics、随机数和概率分布

广泛的分布特别令人感兴趣,它建立在直接从 System.Random 派生的随机数生成器(MersenneTwister 等)之上,所有这些都提供了方便的扩展方法(例如 NextFullRangeInt32、NextFullRangeInt64、NextDecimal 等)。当然,您可以只使用默认的 SystemRandomSource,它只是使用扩展方法修饰的 System.Random。

哦,如果需要,您可以将 RNG 实例创建为线程安全的。

确实很方便!

这是一个老问题,但对于那些刚刚阅读它的人来说,为什么要重新发明轮子呢?

于 2014-02-05T01:40:57.477 回答
1

老实说,我不相信 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,在小数点前最大可能的 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.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;
    }
}
于 2015-11-22T06:57:04.063 回答
0

我想生成最多 9 位小数的“随机”小数。我的方法是只生成一个双精度数并将其除以小数。

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”

于 2018-01-04T11:57:27.487 回答
-1
    public static decimal RandomDecimal()
    {
        int a = RandomNumber(2, 10);
        int b = RandomNumber(0, 99);

        string c = a + "." + b;
        decimal d = decimal.Parse(c);
        return d;
    }

    public static int RandomNumber(int min, int max)
    {
        return _random.Next(min, max);
    }
于 2022-01-25T18:29:42.207 回答