我怎样才能得到一个随机的 System.Decimal?System.Random
不直接支持。
13 回答
编辑:删除旧版本
这类似于 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);
}
您通常会期望随机数生成器不仅生成随机数,而且这些数字是均匀随机生成的。
对于具有有限数量的不同可能结果的随机数生成器,离散均匀随机是有意义的。例如生成一个介于 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);
}
基本上,我们确保比例值的选择与相应范围的大小成比例。
这意味着我们应该得到 0 90% 的时间比例 - 因为该范围包含 90% 的可能范围 - 1 9% 的时间比例等等。
实现仍然存在一些问题,因为它没有考虑到某些数字具有多种表示形式 - 但它应该比其他实现更接近均匀分布。
我知道这是一个老问题,但是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);
}
通过简单的东西的力量,它也可以做到:
var rand = new Random();
var item = new decimal(rand.NextDouble());
这是具有 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);
}
我对此感到困惑。这是我能想到的最好的:
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,因此不可能包含完整的小数范围。
给你...使用 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);
}
由于 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);
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
查看以下链接以获取应有帮助的现成实现:
广泛的分布特别令人感兴趣,它建立在直接从 System.Random 派生的随机数生成器(MersenneTwister 等)之上,所有这些都提供了方便的扩展方法(例如 NextFullRangeInt32、NextFullRangeInt64、NextDecimal 等)。当然,您可以只使用默认的 SystemRandomSource,它只是使用扩展方法修饰的 System.Random。
哦,如果需要,您可以将 RNG 实例创建为线程安全的。
确实很方便!
这是一个老问题,但对于那些刚刚阅读它的人来说,为什么要重新发明轮子呢?
老实说,我不相信 C# 小数的内部格式会像许多人认为的那样工作。出于这个原因,这里提出的至少一些解决方案可能无效或可能无法始终如一地工作。考虑以下 2 个数字以及它们如何以十进制格式存储:
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。
进一步的实验证明,以下代码将变量“Dec”设置为值 0.9999999999999999999999999999m:
double DblH = 0.99999999999999d;
double DblL = 0.99999999999999d;
decimal Dec = (decimal)DblH + (decimal)DblL / 1E14m;
正是从这个发现中,我提出了 Random 类的扩展,可以在下面的代码中看到。我相信这段代码功能齐全,工作状态良好,但很高兴其他人能检查它是否有错误。我不是统计学家,所以我不能说这段代码是否会产生真正均匀的小数分布,但如果我不得不猜测,我会说它没有完美但非常接近(如 51 万亿次中的 1 次支持一定范围的数字)。
第一个 NextDecimal() 函数应产生等于或大于 0.0m 且小于 1.0m 的值。do/while 语句通过循环防止 RandH 和 RandL 超过值 0.99999999999999d,直到它们低于该值。我相信这个循环重复的几率是 51 万亿分之一(强调相信这个词,我不相信我的数学)。这反过来应该防止函数将返回值四舍五入到 1.0m。
第二个 NextDecimal() 函数应该与 Random.Next() 函数一样工作,只是使用十进制值而不是整数。我实际上没有使用过第二个 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;
}
}
我想生成最多 9 位小数的“随机”小数。我的方法是只生成一个双精度数并将其除以小数。
int randomInt = rnd.Next(0, 100);
double randomDouble = rnd.Next(0, 999999999);
decimal randomDec = Convert.ToDecimal(randomint) + Convert.ToDecimal((randomDouble/1000000000));
“randomInt”是小数点前的数字,您可以只输入 0。要减少小数点,只需删除随机的“9”和除法的“0”
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);
}