在C#中生成一个随机十进制
我怎样才能得到一个随机System.Decimal? System.Random
不直接支持它。
编辑:删除旧版本
这与丹尼尔的版本类似,但会给出完整的范围。 它还引入了一个新的扩展方法来获得一个随机的“任何整数”值,我认为这是很方便的。
请注意,这里的小数分布是不统一的 。
/// <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); }
你通常会从随机数发生器中得到它不仅产生随机数的问题,而且这些数是随机产生的。
一致随机有两种定义: 离散均匀随机和连续均匀随机 。
对于具有有限数量的不同可能结果的随机数发生器而言,离散均匀随机是有意义的。 例如,生成一个1到10之间的整数。然后,您可以期望获得4的概率与获得7的概率相同。
当随机数发生器产生一定范围内的数字时,连续均匀随机是有意义的。 例如,生成一个介于0和1之间的实数的生成器。然后,您会期望得到0到0.5之间的数字的概率与获得0.5到1之间的数字的概率相同。
当一个随机数发生器产生浮点数(这基本上是一个System.Decimal是什么 – 它只是基于10的浮点数),那么一致随机的正确定义是什么是有争议的:
一方面,由于计算机中的浮点数由固定的位数表示,所以显然有可能的结果数目是有限的。 所以人们可以争辩说,正确的分布是一个离散的连续分布,每个可表示的数字具有相同的概率。 这基本上是Jon Skeet和John Leidegren的实现。
另一方面,有人可能会争辩说,由于浮点数应该是一个实数的近似值,我们可以通过尝试近似连续随机数发生器的行为来获得更好的结果 – 尽pipe实际的RNG是实际上离散。 这是你从Random.NextDouble()得到的行为,其中 – 尽pipe0.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); }
你也可以讨论如何在整个小数范围内得到均匀的分布。 可能有一个更简单的方法来做到这一点,但约翰·莱德格伦的答案稍作修改应该产生一个相对均匀的分布:
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); }
基本上,我们确保规模值是根据相应范围的大小来select的。
这意味着我们应该得到90%的时间范围,因为这个范围包含了90%的可能范围,这个时间范围是19%。
这个实现还存在一些问题,因为它确实考虑到了一些数字有多个表示 – 但是它应该比其他的实现更接近统一的分布。
这里是随着范围实施,对我来说工作正常的十进制随机。
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); }
我对此感到困惑。 这是我能想到的最好的:
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,所以小数的完整范围是不可能的。
它也是通过简单的东西的力量来做:
var rand = new Random(); var item = new decimal(rand.NextDouble());
在这里你去…使用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); }
查看以下适用于现成实现的链接:
math,随机数和概率分布
广泛的分布特别感兴趣,build立在直接从System.Random派生的随机数发生器(MersenneTwister等)之上,都提供了方便的扩展方法(例如NextFullRangeInt32,NextFullRangeInt64,NextDecimal等)。 当然,您可以使用默认的SystemRandomSource,它只是System.Random的扩展方法。
呵呵,如果你需要,你可以创build你的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#十进制的内部格式是很多人想的方式。 出于这个原因,这里提出的至less一些解决scheme可能无效或可能不一致工作。 考虑以下两个数字以及它们如何以十进制格式存储:
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。
进一步的实validation明下面的代码将variables“Dec”设置为值0.9999999999999999999999999999m:
double DblH = 0.99999999999999d; double DblL = 0.99999999999999d; decimal Dec = (decimal)DblH + (decimal)DblL / 1E14m;
正是从这个发现,我想出了随机类的扩展,可以在下面的代码中看到。 我相信这个代码function齐全,运行良好,但是如果其他人愿意检查错误,我们会很高兴。 我不是一个统计学家,所以我不能说这个代码是否产生了一个真正的统一的小数分布,但是如果我不得不猜测,我会说它完美失败,但是非常接近(如51个亿中的1个呼叫,一定数量的范围)。
第一个NextDecimal()函数应该产生等于或大于0.0m且小于1.0m的值。 do / while语句阻止RandH和RandL通过循环超出值0.99999999999999d,直到它们低于该值。 我相信这个循环重复的几率是51万亿分之一(强调这个词相信,我不相信我的math)。 这反过来应该防止函数将返回值四舍五入到1.0m。
第二个NextDecimal()函数应该和Random.Next()函数一样,只用十进制值而不用整数。 我实际上并没有使用这第二个NextDecimal()函数,并没有testing它。 它相当简单,所以我认为它是正确的,但是,我还没有testing它 – 所以你会想要确保它依靠它之前正常工作。
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; } }