如何获得随机的System.Decimal?System.Random
不直接支持它.
编辑:删除旧版本
这与Daniel的版本类似,但会给出完整的范围.它还引入了一种新的扩展方法来获取随机的"任何整数"值,我认为这很方便.
请注意,此处的小数位分布不均匀.
////// Returns an Int32 with a random value across the entire range of /// possible values. /// 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的实现.
另一方面,有人可能会争辩说,由于浮点数应该是实数的近似值,我们最好通过尝试近似连续随机数生成器的行为来实现 - 即使实际的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); }