在 C# 中生成随机十进制

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/609501/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 10:11:30  来源:igfitidea点击:

Generating a Random Decimal in C#

c#.netrandomdecimal

提问by Daniel Ballinger

How can I get a random System.Decimal? System.Randomdoesn't support it directly.

我怎样才能得到一个随机的 System.Decimal?System.Random不直接支持。

采纳答案by Jon Skeet

EDIT: Removed old version

编辑:删除旧版本

This is similar to Daniel's version, but will give the complete range. It also introduces a new extension method to get a random "any integer" value, which I think is handy.

这类似于 Daniel 的版本,但会提供完整的范围。它还引入了一种新的扩展方法来获取随机的“任意整数”值,我认为这很方便。

Note that the distribution of decimals here is not uniform.

请注意,这里的小数分布并不统一

/// <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);
}

回答by Daniel Ballinger

I puzzled with this for a bit. This is the best I could come up with:

我对此有点困惑。这是我能想到的最好的:

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;
        }
    }

Edit: As noted in the comments lo, mid and hi can never contain int.MaxValue so the complete range of Decimals isn't possible.

编辑:如评论 lo、mid 和 hi 中所述,永远不能包含 int.MaxValue,因此不可能包含完整的小数范围。

回答by Muad'Dib

here you go... uses the crypt library to generate a couple of random bytes, then convertes them to a decimal value... see MSDN for the decimal constructor

在这里......使用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);
}

revised to use a different decimal constructor to give a better range of numbers

修改为使用不同的十进制构造函数以提供更好的数字范围

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);
    }

回答by Rasmus Faber

You would normally expect from a random-number-generator that it not only generated random numbers, but that the numbers were uniformly randomly generated.

您通常会期望随机数生成器不仅生成随机数,而且这些数字是均匀随机生成的。

There are two definitions of uniformly random: discrete uniformly randomand continuous uniformly random.

均匀随机有两种定义:离散均匀随机连续均匀随机

Discretely uniformly random makes sense for a random number generator that has a finite number of different possible outcomes. For example generating an integer between 1 and 10. You would then expect that the probability of getting 4 is the same as getting 7.

对于具有有限数量的不同可能结果的随机数生成器来说,离散均匀随机是有意义的。例如,生成 1 到 10 之间的整数。然后您会期望得到 4 的概率与得到 7 的概率相同。

Continuously uniformly random makes sense when the random number generator generates numbers in a range. For example a generator that generates a real number between 0 and 1. You would then expect that the probability of getting a number between 0 and 0.5 is the same as getting a number between 0.5 and 1.

当随机数生成器生成一个范围内的数字时,连续均匀随机是有意义的。例如,生成 0 到 1 之间的实数的生成器。然后您会期望得到 0 到 0.5 之间的数的概率与得到 0.5 到 1 之间的数的概率相同。

When a random number generator generates floating-point numbers (which is basically what a System.Decimal is - it is just floating-point which base 10), it is arguable what the proper definition of uniformly random is:

当随机数生成器生成浮点数(这基本上就是 System.Decimal 是 - 它只是以 10 为基数的浮点数)时,均匀随机的正确定义是有争议的:

On one hand, since the floating-point number is being represented by a fixed number of bits in a computer, it is obvious that there are a finite number of possible outcomes. So one could argue that the proper distribution is a discrete continuous distribution with each representable number having the same probability. That is basically what Jon Skeet'sand John Leidegren'simplementation does.

一方面,由于浮点数在计算机中由固定数量的位表示,很明显,可能的结果数量是有限的。因此,人们可能会争辩说,正确的分布是一种离散连续分布,每个可表示的数字都具有相同的概率。这基本上就是Jon SkeetJohn Leidegren 的实现所做的。

On the other hand, one might argue that since a floating-point number is supposed to be an approximation to a real number, we would be better off by trying to approximate the behavior of a continuous random number generator - even though are actual RNG is actually discrete. This is the behavior you get from Random.NextDouble(), where - even though there are approximately as many representable numbers in the range 0.00001-0.00002 as there are in the range 0.8-0.9, you are a thousand times more likely to get a number in the second range - as you would expect.

另一方面,有人可能会争辩说,由于浮点数应该是实数的近似值,我们最好尝试近似连续随机数生成器的行为——即使实际 RNG 是实际上是离散的。这是您从 Random.NextDouble() 获得的行为,其中 - 即使 0.00001-0.00002 范围内的可表示数字与 0.8-0.9 范围内的可表示数字大致一样多,您获得第二个范围内的数字 - 正如您所期望的。

So a proper implementation of a Random.NextDecimal() should probably be continuously uniformly distributed.

因此, Random.NextDecimal() 的正确实现可能应该是连续均匀分布的。

Here is a simple variation of Jon Skeet's answer that is uniformly distributed between 0 and 1 (I reuse his NextInt32() extension method):

这是 Jon Skeet 答案的一个简单变体,它在 0 和 1 之间均匀分布(我重用了他的 NextInt32() 扩展方法):

public static decimal NextDecimal(this Random rng)
{
     return new decimal(rng.NextInt32(), 
                        rng.NextInt32(),
                        rng.Next(0x204FCE5E),
                        false,
                        0);
}

You could also discuss how to get an uniform distribution over the entire range of decimals. There is probably an easier way to do this, but this slight modification of John Leidegren's answershould produce a relatively uniform distribution:

您还可以讨论如何在整个小数范围内获得均匀分布。可能有一种更简单的方法可以做到这一点,但是对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);
}

Basically, we make sure that values of scale are chosen proportionally to the size of the corresponding range.

基本上,我们确保比例值的选择与相应范围的大小成比例。

That means that we should get a scale of 0 90% of the time - since that range contains 90% of the possible range - a scale of 1 9% of the time, etc.

这意味着我们应该得到 0 90% 的时间比例 - 因为该范围包含可能范围的 90% - 1 9% 的时间比例,等等。

There are still some problems with the implementation, since it does take into account that some numbers have multiple representations - but it should be much closer to a uniform distribution than the other implementations.

实现仍然存在一些问题,因为它确实考虑了某些数字具有多种表示形式 - 但它应该比其他实现更接近均匀分布。

回答by Rasmus Faber

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

回答by Danil

Here is Decimal random with Range implementation that works fine for me.

这是带有范围实现的十进制随机数,对我来说效果很好。

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);
}

回答by Ben Stabile

Check out the following link for ready-made implementations that should help:

查看以下链接,了解应该有帮助的现成实现:

MathNet.Numerics, Random Numbers and Probability Distributions

MathNet.Numerics、随机数和概率分布

The extensive distributions are especially of interest, built on top of the Random Number Generators (MersenneTwister, etc.) directly derived from System.Random, all providing handy extension methods (e.g. NextFullRangeInt32, NextFullRangeInt64, NextDecimal, etc.). You can, of course, just use the default SystemRandomSource, which is simply System.Random embellished with the extension methods.

广泛的分布尤其令人感兴趣,它们建立在直接从 System.Random 派生的随机数生成器(MersenneTwister 等)之上,都提供了方便的扩展方法(例如 NextFullRangeInt32、NextFullRangeInt64、NextDecimal 等)。当然,您可以只使用默认的 SystemRandomSource,它只是用扩展方法修饰的 System.Random。

Oh, and you can create your RNG instances as thread safe if you need it.

哦,如果需要,您可以将 RNG 实例创建为线程安全的。

Very handy indeed!

确实很方便!

This is an old question, but for those who are just reading it, why re-invent the wheel?

这是一个老问题,但对于那些刚刚阅读它的人来说,为什么要重新发明轮子呢?

回答by Bryan Loeper

I know this is an old question, but the distribution issue Rasmus Faber describedkept bothering me so I came up with the following. I have not looked in depth at the NextInt32 implementation provided by Jon Skeetand am assuming (hoping) it has the same distribution as Random.Next().

我知道这是一个老问题,但是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);
}

回答by Darin

To be honest I don't believe the internal format of the C# decimal works the way many people think. For this reason at least some of the solutions presented here are possibly invalid or may not work consistently. Consider the following 2 numbers and how they are stored in the decimal format:

老实说,我不相信 C# 十进制的内部格式和很多人想的一样。出于这个原因,至少这里提供的一些解决方案可能无效或可能无法始终如一地工作。考虑以下 2 个数字以及它们如何以十进制格式存储:

0.999999999999999m
Sign: 00
96-bit integer: 00 00 00 00 FF 7F C6 A4 7E 8D 03 00
Scale: 0F

and

0.9999999999999999999999999999m
Sign: 00
96-bit integer: 5E CE 4F 20 FF FF FF 0F 61 02 25 3E
Scale: 1C

Take special note of how the scale is different but both values are nearly the same, that is, they are both less than 1 by only a tiny fraction. It appears that it is the scale and the number of digits that have a direct relationship. Unless I'm missing something, this should throw a monkey wrench into most any code that tampers with the 96-bit integer part of a decimal but leaves the scale unchanged.

请特别注意比例是如何不同的,但两个值几乎相同,即它们都小于 1 仅一小部分。看来是比例尺和位数有直接关系。除非我遗漏了什么,否则这应该会在大多数篡改小数的 96 位整数部分但保持比例不变的代码中投入一个猴子扳手。

In experimenting I found that the number 0.9999999999999999999999999999m, which has 28 nines, has the maximum number of nines possible before the decimal will round up to 1.0m.

在实验中,我发现数字 0.9999999999999999999999999999m 有 28 个 9,在小数点四舍五入到 1.0m 之前,9 的数量最多。

Further experimenting proved the following code sets the variable "Dec" to the value 0.9999999999999999999999999999m:

进一步实验证明以下代码将变量“Dec”设置为值 0.9999999999999999999999999999m:

double DblH = 0.99999999999999d;
double DblL = 0.99999999999999d;
decimal Dec = (decimal)DblH + (decimal)DblL / 1E14m;

It is from this discovery that I came up with the extensions to the Random class that can be seen in the code below. I believe this code is fully functional and in good working order, but would be glad for other eyes to be checking it for mistakes. I'm not a statistician so I can't say if this code produces a truly uniform distribution of decimals, but if I had to guess I would say it fails perfection but comes extremely close (as in 1 call out of 51 trillion favoring a certain range of numbers).

正是从这个发现中,我想出了 Random 类的扩展,可以在下面的代码中看到。我相信这段代码功能齐全且工作正常,但很高兴其他人能检查它是否有错误。我不是统计学家,所以我不能说这段代码是否产生了真正均匀的小数分布,但如果我不得不猜测,我会说它不完美但非常接近(如 51 万亿次呼叫中有 1 次偏向于一定范围的数字)。

The first NextDecimal() function should produce values equal to or greater than 0.0m and less than 1.0m. The do/while statement prevents RandH and RandL from exceeding the value 0.99999999999999d by looping until they are below that value. I believe the odds of this loop ever repeating are 1 in 51 trillion (emphasis on the word believe, I don't trust my math). This in turn should prevent the functions from ever rounding the return value up to 1.0m.

第一个 NextDecimal() 函数应生成等于或大于 0.0m 且小于 1.0m 的值。do/while 语句通过循环来防止 RandH 和 RandL 超过值 0.99999999999999d 直到它们低于该值。我相信这个循环重复的几率是 51 万亿分之一(强调相信这个词,我不相信我的数学)。这反过来应该防止函数将返回值四舍五入到 1.0m。

The second NextDecimal() function should work the same as the Random.Next() function, only with Decimal values instead of integers. I actually haven't been using this second NextDecimal() function and haven't tested it. Its fairly simple so I think I have it right, but again, I haven't tested it - so you will want to make sure it is working correctly before relying on it.

第二个 NextDecimal() 函数应该与 Random.Next() 函数的工作方式相同,只是使用 Decimal 值而不是整数。我实际上没有使用过第二个 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;
    }
}

回答by Adam Wells

It is also, through the power of easy stuff, to do:

通过简单的东西的力量,它也可以做到:

var rand = new Random();
var item = new decimal(rand.NextDouble());