在 C # 中生成随机小数

我怎样才能得到一个随机的系统。十进制? System.Random不支持它直接。

77867 次浏览

我对此困惑了一会儿,这是我能想到的最好的办法:

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 的全部范围是不可能的。

编辑: 删除旧版本

这是类似丹尼尔的版本,但将给出完整的范围。它还引入了一种新的扩展方法来获取随机的“任意整数”值,我认为这很方便。

注意,这里的小数分布是 不是统一的

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

好了... 使用 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);
}

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

一致随机有两个定义: 离散的一致随机的连续一致随机性

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

当随机数生成器生成一个范围内的数字时,连续一致随机是有意义的。例如,生成0到1之间的实数的生成器。然后你会期望得到一个介于0和0.5之间的数字的概率和得到一个介于0.5和1之间的数字的概率是一样的。

当一个随机数生成器生成浮点数时(这基本上就是一个系统。十进制是——它只是以10为基数的浮点数) ,一致随机的正确定义是什么值得商榷:

一方面,由于浮点数是由计算机中固定数量的比特来表示的,因此显然存在有限数量的可能结果。因此,我们可以认为,适当的分布是一个离散的连续分布,每个可表示的数字具有相同的概率。这基本上就是 Jon Skeet 的约翰 · 莱德格伦的实现所做的。

另一方面,有人可能会争辩说,由于浮点数应该是实数的近似值,我们最好尝试近似连续随机数生成器的行为——即使实际的 RNG 实际上是离散的。这就是你从 Random 得到的行为。NextDouble () ,即使在0.00001-0.00002范围内有大约0.8-0.9范围内的数字,你在第二个范围内得到数字的可能性是你预期的1000倍。

因此,一个 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% 的时间范围,等等。

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

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

下面是使用 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);
}

以下连结载有现成的实施方案,应该会有所帮助:

数值,随机数和概率分布

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

哦,如果需要的话,还可以创建线程安全的 RNG 实例。

确实很方便!

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

我知道这是一个老问题,但 分配问题 Rasmus Faber 描述一直困扰着我,所以我想出了以下几个问题。我还没有深入研究的 由 Jon Skeet 提供的 NextInt32实现和我假设(希望)它有同样的分布作为 随机,下一个

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

老实说,我不相信 C # 小数的内部格式能够像很多人想的那样工作。由于这个原因,至少这里提出的一些解决方案可能是无效的,或者可能不能一致地工作。考虑以下两个数字以及它们是如何以十进制格式存储的:

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.99999999999999999999999999999999999999999 m 有28个9,在小数点四舍五入到1.0 m 之前,可能有最多的9。

进一步的实验证明,下面的代码将变量“ Dec”设置为值0.99999999999999999999999 m:

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

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

第一个 NextDecimal ()函数应该生成等于或大于0.0 m、小于1.0 m 的值。Do/while 语句通过循环阻止 RandH 和 RandL 超过值0.9999999999999 d,直到它们低于该值。我相信这个循环重复的几率是51万亿分之一(强调“相信”这个词,我不相信我的数学)。这反过来又可以防止函数将返回值四舍五入到1.0 m。

第二个 NextDecimal ()函数的工作原理应该与 Random 相同。函数,只使用 Decimal 值而不是整数。实际上,我还没有使用过第二个 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;
}
}

通过简单事物的力量,我们也可以做到:

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

我想生成最多9位小数的“随机”小数。我的方法是生成一个双精度数,然后除以小数。

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


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

就是小数点后面的数字,你可以写0。 要减少小数点,只需删除“9”在随机和“0”在分

因为 OP 的问题是非常广泛的,只是想要一个随机系统。十进制没有任何限制,下面是一个非常简单的解决方案,我的工作。

我并不关心生成的数字的一致性或精度,所以如果你有一些限制,这里的其他答案可能会更好,但这个在简单的情况下工作得很好。

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);
    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);
}
System.Random rnd = new System.Random();
System.Math.Round( Convert.ToDecimal(rnd.NextDouble() * (maxValue-minValue) + minValue) , decimalPlaces);

泛线总是最好的完整代码和下面的例子

using System;
                    

public class Program
{
public static void Main()
{
var val = GetRandomDecimal(0, 100, 2);
Console.WriteLine(val.ToString());
}


    

static System.Random rnd = new System.Random();
public static decimal GetRandomDecimal(int minValue, int maxValue, int decimalPlaces)
{
return System.Math.Round( Convert.ToDecimal(rnd.NextDouble() * (maxValue-minValue) + minValue) , decimalPlaces);
}
        

}

Https://dotnetfiddle.net/wt5xez