【问题标题】:Best way to generate a random float in C# [closed]在 C# 中生成随机浮点数的最佳方法 [关闭]
【发布时间】:2010-07-29 17:26:50
【问题描述】:

在 C# 中生成随机浮点数的最佳方法是什么?

更新:我想要从 float.Minvalue 到 float.Maxvalue 的随机浮点数。我在一些数学方法的单元测试中使用这些数字。

【问题讨论】:

  • 4.0 - 由公平的浮点骰子随机选择。
  • @JesseC.Slicer 您忘记提供链接:xkcd.com/221
  • 我有一个用于“离散浮点数”,甚至可以缩放到最小/最大 - 请重新打开以获得新答案。
  • 如果有人在 Unity 环境中提出这个问题,Random.Range(float.Minvalue, float.Maxvalue)

标签: c# random floating-point


【解决方案1】:

最好的方法,没有疯狂的值,distributed with respect to the representable intervals on the floating-point number line(删除了“统一”,因为对于连续的数字线,它显然是不统一的):

static float NextFloat(Random random)
{
    double mantissa = (random.NextDouble() * 2.0) - 1.0;
    // choose -149 instead of -126 to also generate subnormal floats (*)
    double exponent = Math.Pow(2.0, random.Next(-126, 128));
    return (float)(mantissa * exponent);
}

(*) ... 检查here 是否有低于正常值的浮点数

警告:也会产生正无穷大!为了安全起见,选择 127 的指数。

另一种方法会给你一些疯狂的值(位模式的均匀分布),可能对模糊测试有用:

static float NextFloat(Random random)
{
    var buffer = new byte[4];
    random.NextBytes(buffer);
    return BitConverter.ToSingle(buffer,0);
}

对以前版本的改进是这个,它不会创建“疯狂”值(既不是无穷大也不是 NaN)并且仍然很快(也相对于浮点数线上的可表示间隔分布):

public static float Generate(Random prng)
{
    var sign = prng.Next(2);
    var exponent = prng.Next((1 << 8) - 1); // do not generate 0xFF (infinities and NaN)
    var mantissa = prng.Next(1 << 23);

    var bits = (sign << 31) + (exponent << 23) + mantissa;
    return IntBitsToFloat(bits);
}

private static float IntBitsToFloat(int bits)
{
    unsafe
    {
        return *(float*) &bits;
    }
}

最没用的方法:

static float NextFloat(Random random)
{
    // Not a uniform distribution w.r.t. the binary floating-point number line
    // which makes sense given that NextDouble is uniform from 0.0 to 1.0.
    // Uniform w.r.t. a continuous number line.
    //
    // The range produced by this method is 6.8e38.
    //
    // Therefore if NextDouble produces values in the range of 0.0 to 0.1
    // 10% of the time, we will only produce numbers less than 1e38 about
    // 10% of the time, which does not make sense.
    var result = (random.NextDouble()
                  * (Single.MaxValue - (double)Single.MinValue))
                  + Single.MinValue;
    return (float)result;
}

浮点数行来自:Intel Architecture Software Developer's Manual Volume 1: Basic Architecture. Y 轴是对数(以 2 为底),因为连续的二进制浮点数不是线性不同的。

【讨论】:

  • 似乎没有 BitConverter.GetSingle 方法。你是说ToSingle吗?
  • 使用随机字节很容易得到 NaN 值,并且分布很可能是不均匀的。 (我不想预测分布。)
  • 同意,第二个对于测试包括 NaN 在内的潜在浮点值的范围很有用。看来这种方法会产生大约 0.25%-0.4% 的 NaN。
  • 有趣的是,使用 Single.MinValue/Single.MaxValue 作为范围会产生非常差的数字分布。
  • @sixlettervariables:你这样看起来更漂亮的原因是因为你给出了一个对数刻度。在线性范围内,我相信 NextDouble * 范围将是统一的。
【解决方案2】:

有什么理由不使用Random.NextDouble 然后转换为float?这会给你一个介于 0 和 1 之间的浮点数。

如果您想要不同形式的“最佳”,则需要指定您的要求。请注意,Random 不应用于金融或安全等敏感问题 - 通常您应该在整个应用程序中重用现有实例,或每个线程一个(因为 Random 不是线程安全的)。

编辑:按照 cmets 中的建议,将其转换为 float.MinValuefloat.MaxValue 的范围:

// Perform arithmetic in double type to avoid overflowing
double range = (double) float.MaxValue - (double) float.MinValue;
double sample = rng.NextDouble();
double scaled = (sample * range) + float.MinValue;
float f = (float) scaled;

编辑:现在您已经提到这是用于单元测试的,我不确定这是一种理想的方法。您可能应该使用具体值进行测试 - 确保使用每个相关类别中的样本进行测试 - 无穷大、NaN、非正规数、非常大的数字、零等。

【讨论】:

  • 我认为 NextDouble 只提供从 0.0 到 1.0 的值,我想要比这更大的范围(比如从 float.MinValue 到 float.MaxValue)。我想我应该指定:)
  • 只需将其乘以 (MaxValue-MinValue) 并添加 MinValue 即可?所以像 Random.NextDouble *(float.MaxValue-float.MinValue) + float.MinValue
  • 产生的值的分布是双峰的,我认为这与值在该范围内的大小有关。
  • 我终于明白了为什么这种方法不会产生二进制浮点数的预期值分布。如果 NextDouble() 返回 0 和 1 之间的均匀分布,则只有 10% 的值会在 0 和 0.1 之间。所以只有 10% 的时间数字会小于 1e37,这不是预期的分布。
  • @sixlettervariables:这个“预期分布”在哪里指定?我预期的分布是统一的,我认为没有理由相信这不会发生。
【解决方案3】:

还有一个版本……(我觉得这个还不错)

static float NextFloat(Random random)
{
    (float)(float.MaxValue * 2.0 * (rand.NextDouble()-0.5));
}

//inline version
float myVal = (float)(float.MaxValue * 2.0 * (rand.NextDouble()-0.5));

我觉得这个……

  • 第二快(参见基准)
  • 均匀分布

还有一个版本...(不是那么好,但还是发布了)

static float NextFloat(Random random)
{
    return float.MaxValue * ((rand.Next() / 1073741824.0f) - 1.0f);
}

//inline version
float myVal = (float.MaxValue * ((rand.Next() / 1073741824.0f) - 1.0f));

我觉得这个……

  • 是最快的(参见基准)
  • 是均匀分布的,但是因为 Next() 是一个 31 位随机值,它只会返回 2^31 个值。 (50% 的相邻值将具有相同的值)

本页大部分功能测试:(i7,发布,无调试,2^28循环)

 Sunsetquest1: min: 3.402823E+38  max: -3.402823E+38 time: 3096ms
 SimonMourier: min: 3.402823E+38  max: -3.402819E+38 time: 14473ms
 AnthonyPegram:min: 3.402823E+38  max: -3.402823E+38 time: 3191ms
 JonSkeet:     min: 3.402823E+38  max: -3.402823E+38 time: 3186ms
 Sixlettervar: min: 1.701405E+38  max: -1.701410E+38 time: 19653ms
 Sunsetquest2: min: 3.402823E+38  max: -3.402823E+38 time: 2930ms

【讨论】:

  • 你确定它会更快,因为它是 2 的幂吗?我会假设它会被隐式转换为浮点 (它不能准确地表示该值,然后使用常规浮点除法)。
  • 感谢您注意到这一点,这是一个很好的收获!没错,它首先被转换为浮点数,因此编译器无法使用移位指令。我删除了“更快,因为它是 2 的幂”。如您所知,我进行了其他几项更改,包括添加新功能和进行基准测试。
【解决方案4】:

我采取了与其他人略有不同的方法

static float NextFloat(Random random)
{
    double val = random.NextDouble(); // range 0.0 to 1.0
    val -= 0.5; // expected range now -0.5 to +0.5
    val *= 2; // expected range now -1.0 to +1.0
    return float.MaxValue * (float)val;
}

cmets 解释了我在做什么。获取下一个双精度数,将该数字转换为介于 -1 和 1 之间的值,然后将其与 float.MaxValue 相乘。

【讨论】:

    【解决方案5】:

    另一种解决方案是这样做:

    static float NextFloat(Random random)
    {
        float f;
        do
        {
            byte[] bytes = new byte[4];
            random.NextBytes(bytes);
            f = BitConverter.ToSingle(bytes, 0);
        }
        while (float.IsInfinity(f) || float.IsNaN(f));
        return f;
    }
    

    【讨论】:

      【解决方案6】:

      这是我想出的另一种方法: 假设您想获得一个介于 5.5 和 7 之间的浮点数,小数点后 3 位。

      float myFloat;
      int myInt;
      System.Random rnd = new System.Random();
      
      void GenerateFloat()
      {
      myInt = rnd.Next(1, 2000);
      myFloat = (myInt / 1000) + 5.5f;
      }
      

      这样你总是会得到一个大于 5.5 和一个小于 7 的数字。

      【讨论】:

        【解决方案7】:

        我更喜欢使用下面的代码来生成一个小数点后的十进制数。您可以通过在字符串“组合”中附加该数字来复制粘贴第 3 行以在小数点后添加更多数字。您可以通过将 0 和 9 更改为您的首选值来设置最小值和最大值。

        Random r = new Random();
        string beforePoint = r.Next(0, 9).ToString();//number before decimal point
        string afterPoint = r.Next(0,9).ToString();//1st decimal point
        //string secondDP = r.Next(0, 9).ToString();//2nd decimal point
        string combined = beforePoint+"."+afterPoint;
        decimalNumber= float.Parse(combined);
        Console.WriteLine(decimalNumber);
        

        希望对你有所帮助。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-11-12
          • 1970-01-01
          • 2012-03-17
          • 2012-11-04
          • 1970-01-01
          • 2010-10-15
          相关资源
          最近更新 更多