如何获取一个随机的 System.Decimal?System.Random
并不直接支持它。
如何获取一个随机的 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);
}
Next()
永远不会返回负数。 NextInt32
可以返回任何 int
。 - Jon Skeet通过易于操作的工具,也可以轻松实现:
var rand = new Random();
var item = new decimal(rand.NextDouble());
您通常期望随机数生成器不仅生成随机数字,而且这些数字是均匀随机生成的。
对于具有有限数量不同可能结果的随机数生成器,离散均匀随机是有意义的。例如,在1和10之间生成一个整数。然后您期望得到4的概率与得到7的概率相同。
当随机数生成器在范围内生成数字时,连续均匀随机是有意义的。例如,在0和1之间生成实数的生成器。然后您期望得到介于0和0.5之间的数字的概率与得到介于0.5和1之间的数字的概率相同。
当随机数生成器生成浮点数时(基本上就是System.Decimal - 它只是带有10进制基数的浮点数),均匀随机的正确定义是有争议的:
一方面,由于计算机中浮点数是用固定数量的位表示的,很明显有有限数量的可能结果。因此,有人认为适当的分布是具有相同概率的离散连续分布,每个可表示数字都属于这种分布。这基本上就是Jon Skeet和John Leidegren的实现方法。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);
}
28 * r.NextDouble()
有何不同? - Hosam Alyr.Next(2)==0
似乎比 r.NextDouble()>=0.5
更快。 - D.R.GetDecimalScale
的速度,最好只需调用一次Random
。有什么想法或指针吗? - D.R.//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 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);
}
我对此感到困惑了一段时间。这是我能想到的最好解释:
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,因此无法完全涵盖所有Decimal范围。
由于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);
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
double DblH = 0.99999999999999d;
double DblL = 0.99999999999999d;
decimal Dec = (decimal)DblH + (decimal)DblL / 1E14m;
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;
}
}
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
在这里...使用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);
}