如何在C#中生成0到1之间的随机数?

28

我想要得到一个介于1和0之间的随机数。然而,每次我都得到0。有人可以解释一下为什么我每次都得到0吗?这是我尝试过的代码。

Random random = new Random();
int test = random.Next(0, 1);
Console.WriteLine(test);
Console.ReadKey();

6
只有一个整数,它的值 >= 0< 1。因此,您将获得您所要求的内容。 - user4003407
4
请阅读 Random.Next 的文档并检查您提供的值。如果您想要一个非整数,那么您正在使用错误的调用方式。如果您想要获取 0 或 1,则应将 2 作为上限传递给该函数。 - Jon Skeet
double test = random.NextDouble(); - zahrakhani
6个回答

45
根据文档Next返回一个整数随机数,在最小值(包含)和最大值(不包含)之间:

返回值

一个32位有符号整数,大于或等于minValue且小于maxValue;也就是说,返回值范围包括minValue但不包括maxValue。如果minValue等于maxValue,则返回minValue。

唯一满足条件的整数为

0 <= x < 1

值为0,因此您总是得到值0。换句话说,0是唯一在半开区间[0,1)内的整数。

因此,如果您实际上对整数值01感兴趣,请将上限设为2

var n = random.Next(0, 2);

如果您希望获得0到1之间的小数,请尝试:

var n = random.NextDouble();

12
好的,我猜你想生成一个随机的单位区间。这在数学、统计学、逻辑学和物理学中都被广泛使用,所以这似乎是一个非常合理的要求。
现在,如果这是你想要的,那么这个页面上关于双精度的每一个答案都是错误的。这有点搞笑,因为每个人都在引用文档。如果你使用NextDouble()生成一个双精度数,你得到的不是一个介于0和1之间(包括1)的数字,而是一个介于0和1之间(不包括1)的数字。如果你想要一个单位区间,那就不好了。(也许你只是想抛硬币,你的问题有点含糊不清...) 追加说明(很久以后) 我详细解释了为什么在范围{0,1}内获取一个随机的包含浮点数是非常困难的。如果你只是想解决这个问题并继续生活,可以直接跳到最后一个巧妙的技巧。
回到抱怨
要得到一个双倍,你必须像这样做一些诡计:
public double NextRandomRange(double minimum, double maximum)
{
     Random rand = new Random();
     return rand.NextDouble() * (maximum - minimum) + minimum;
}

然后打电话
NextRandomRange(0,1 + Double.Epsilon);

似乎这样做会有效,对吧?在使用双精度浮点数时,1 + Double.Epsilon应该是1之后的最大数,对吗?这是如何解决整数问题的方法。
嗯.........
我怀疑这样做可能不会正确,因为底层代码将生成一些随机字节,然后通过一些数学技巧将其适应预期范围。简而言之,适用于整数的逻辑在使用浮点数时并不完全适用。
我们来看看,好吗?(链接:https://referencesource.microsoft.com/#mscorlib/system/random.cs,e137873446fcef75)
  /*=====================================Next=====================================
  **Returns: A double [0..1)
  **Arguments: None
  **Exceptions: None
  ==============================================================================*/
  public virtual double NextDouble() {
    return Sample();
  }

Sample()是什么鬼东西?
  /*====================================Sample====================================
  **Action: Return a new random number [0..1) and reSeed the Seed array.
  **Returns: A double [0..1)
  **Arguments: None
  **Exceptions: None
  ==============================================================================*/
  protected virtual double Sample() {
      //Including this division at the end gives us significantly improved
      //random number distribution.
      return (InternalSample()*(1.0/MBIG));
  }

好的,开始有点进展了。顺便说一下,MBIG是Int32.MaxValue(2147483647或2^31-1),这样除法就能得到以下结果:
InternalSample()*0.0000000004656612873077392578125;

好的,InternalSample()到底是什么鬼?
  private int InternalSample() {
      int retVal;
      int locINext = inext;
      int locINextp = inextp;

      if (++locINext >=56) locINext=1;
      if (++locINextp>= 56) locINextp = 1;
      
      retVal = SeedArray[locINext]-SeedArray[locINextp];

      if (retVal == MBIG) retVal--;          
      if (retVal<0) retVal+=MBIG;
      
      SeedArray[locINext]=retVal;

      inext = locINext;
      inextp = locINextp;
                
      return retVal;
  }

嗯...那倒是个问题。但是SeedArray和inext这些东西到底是什么意思?
  private int inext;
  private int inextp;
  private int[] SeedArray = new int[56];

事情开始变得清晰起来。种子数组是一个用于生成值的整数数组。如果你看一下init函数的定义,你会发现有很多位加法和诡计被用来将一个由55个初始准随机值组成的数组随机化。
  public Random(int Seed) {
    int ii;
    int mj, mk;

    //Initialize our Seed array.
    //This algorithm comes from Numerical Recipes in C (2nd Ed.)
    int subtraction = (Seed == Int32.MinValue) ? Int32.MaxValue : Math.Abs(Seed);
    mj = MSEED - subtraction;
    SeedArray[55]=mj;
    mk=1;
    for (int i=1; i<55; i++) {  //Apparently the range [1..55] is special (All hail Knuth!) and so we're skipping over the 0th position.
      ii = (21*i)%55;
      SeedArray[ii]=mk;
      mk = mj - mk;
      if (mk<0) mk+=MBIG;
      mj=SeedArray[ii];
    }
    for (int k=1; k<5; k++) {
      for (int i=1; i<56; i++) {
    SeedArray[i] -= SeedArray[1+(i+30)%55];
    if (SeedArray[i]<0) SeedArray[i]+=MBIG;
      }
    }
    inext=0;
    inextp = 21;
    Seed = 1;
  }

好的,回到InternalSample(),我们现在可以看到随机双精度数是通过两个乱序的32位整数之差生成的,将结果限制在0到2147483647-1的范围内,然后将结果乘以1/2147483647。对种子值列表进行了更多的混淆处理,但本质上就是这样。
(有趣的是,获取范围内任何数字的机会大约是1/r,除了2^31-2,它是2 * (1/r)!所以如果你认为一些愚蠢的编码人员正在使用RandNext()来生成视频扑克机上的数字,你应该总是下注2^32-2!这就是为什么我们不在重要的事情上使用Random的原因之一...)
所以,如果InternalSample()的输出为0,我们将其乘以0.0000000004656612873077392578125,得到0,这是我们范围的下限。如果我们得到2147483646,我们最终得到0.9999999995343387126922607421875,所以说NextDouble产生的结果是[0,1)是...有点对吗?更准确地说,它在[0,0.9999999995343387126922607421875]的范围内。
我上面提出的解决方案将会失败,因为double.Epsilon = 4.94065645841247E-324,远小于0.0000000004656612873077392578125(你需要将其加到我们上面的结果中才能得到1)。
具有讽刺意味的是,如果不是InternalSample()方法中的减一操作:
if (retVal == MBIG) retVal--;

我们可以在返回值中得到1。所以要么你复制Random类中的所有代码并省略retVal--这一行,要么将NextDouble()的输出乘以类似于1.0000000004656612875245796924106的数值,稍微拉伸输出范围以包含1。实际测试这个值可以让我们非常接近,但我不知道我运行的几亿次测试是否只是没有产生2147483646(很有可能),或者是浮点误差渗入了方程中。我怀疑是前者。数百万次测试不太可能产生一个2亿分之一的结果。
// Just try to explain where you got this number during the code review...
NextRandomRange(0,1.0000000004656612875245796924106); 

简而言之?

使用随机双精度浮点数的包含范围是棘手的。

但是,如果你还在跟着我,或者你只是因为在顶部读了一条注释而跳到这里,这里有一个非常简单的解决方案,它能够工作,而且不需要你拥有离散浮点数学的博士学位。

解决方案

如果在{0,1}之间生成一个浮点数非常困难,那么我们就生成一个整数然后进行转换。

float fauxFloat = random.Next(0,11)/10;

哦,狡猾。现在,这样做不会给你太多的精确度,所以我们可以通过简单地添加更多来改进这一点:
public double RandomUnitInterval()
{
    var_Random = new Random();
    int precision = 1000000000;
    return (random.Next(0, precision + 1)) / ((double)precision);
}

如果你将精度设置为10,并循环执行,你会发现我们偶尔会落在0和1两个数之间。任务完成。这个示例方法可以给你大约9位小数点后的精度,所以对于大多数应用来说应该是相当可靠的。
这就是你如何获得一个介于0和1之间的随机数,包括0和1!

11

你可以这样做,但你应该这样做:

double test = random.NextDouble();

如果你想要获得随机整数(0或1),你应该将上限设置为2,因为它是排除的

int test = random.Next(0, 2);

1
您得到零是因为Random.Next(a,b)返回[a,b)范围内的数字,它大于或等于a,并且小于b。

如果您想获得{0,1}中的一个数字,则应使用:

var random = new Random();
var test = random.Next(0, 2);

0

因为您要求的数字小于1

文档中提到:

返回值
一个32位有符号整数,大于或等于minValue且小于maxValue;也就是说,返回值的范围包括minValue但不包括maxValue。如果minValue等于maxValue,则返回minValue。


-1

如果你的目标是从0.0到1.0,那么像这样重写代码

Random random = new Random();

double test = random.NextDouble();

Console.WriteLine(test);

Console.ReadKey();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接