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);
}
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);
}
一方面,由于浮点数是由计算机中固定数量的比特来表示的,因此显然存在有限数量的可能结果。因此,我们可以认为,适当的分布是一个离散的连续分布,每个可表示的数字具有相同的概率。这基本上就是 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);
}
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
//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);
}
正是从这个发现中,我提出了 Random 类的扩展,可以在下面的代码中看到。我相信这段代码功能齐全,工作状态良好,但是如果其他人能检查一下是否有错误,我会很高兴。我不是一个统计学家,所以我不能说这个代码是否产生了一个真正统一的小数分布,但如果我不得不猜测,我会说它没有完美,但非常接近(就像在一个调用51万亿偏爱一定范围的数字)。
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);
}
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);
}
}