【问题标题】:How to get random double value out of random byte array values?如何从随机字节数组值中获取随机双精度值?
【发布时间】:2008-12-31 17:34:54
【问题描述】:

我想使用 RNGCryptoServiceProvider 作为我的随机数来源。由于它只能将它们作为字节值数组输出,我如何将它们转换为 0 到 1 的双精度值,同时保持结果的一致性?

【问题讨论】:

    标签: c# .net random


    【解决方案1】:
    byte[] result = new byte[8];
    rng.GetBytes(result);
    return (double)BitConverter.ToUInt64(result,0) / ulong.MaxValue;
    

    【讨论】:

    • 今天我的大脑无法处理简单的数学运算。这正是我想要做的。 +1 给你。
    • 是的,这好多了... 1.0/value 的情况很糟糕... 你只需要一个 double 值 > 2 就可以从 1 到 0 的一半。还有更多的 double从 2..double.Max 比从 0..2 开始。
    • 谢谢你,迈赫达德!像魅力一样工作!
    • 这个解决方案似乎可以(如果你真的(不)幸运的话)返回 1。但是 Random.NextDouble() 返回的值介于 0 和 1 之间。
    • @bruce965 第一个问题:这是因为ulong.MaxValue 是分子中可以得到的最大数字。 第二个问题: 否,因为分子总是正数(无符号)。 第三个问题: 不知道你在说什么……3 与什么有什么关系? 2^64double.MaxValue 小得多,所以没有Infinity,它可能变成NaN 的唯一方法是分母是否为零,而事实并非如此。 编辑: 也许您认为double 的演员表是在传送?不是。
    【解决方案2】:

    这就是我要这样做的方式。

    private static readonly System.Security.Cryptography.RNGCryptoServiceProvider _secureRng;
    public static double NextSecureDouble()
    {
      var bytes = new byte[8];
      _secureRng.GetBytes(bytes);
      var v = BitConverter.ToUInt64(bytes, 0);
      // We only use the 53-bits of integer precision available in a IEEE 754 64-bit double.
      // The result is a fraction, 
      // r = (0, 9007199254740991) / 9007199254740992 where 0 <= r && r < 1.
      v &= ((1UL << 53) - 1);
      var r = (double)v / (double)(1UL << 53);
      return r;
    }
    

    巧合的是9007199254740991 / 9007199254740992 is ~= 0.99999999999999988897769753748436,这是Random.NextDouble 方法将返回的最大值(参见https://msdn.microsoft.com/en-us/library/system.random.nextdouble(v=vs.110).aspx)。

    一般来说,连续均匀分布的标准差是(max - min) / sqrt(12)。

    对于 1000 个样本量,我可靠地控制在 2% 的误差范围内。

    对于 10000 的样本量,我可以可靠地控制在 1% 的误差范围内。

    这是我验证这些结果的方法。

    [Test]
    public void Randomness_SecureDoubleTest()
    {
      RunTrials(1000, 0.02);
      RunTrials(10000, 0.01);
    }
    
    private static void RunTrials(int sampleSize, double errorMargin)
    {
      var q = new Queue<double>();
    
      while (q.Count < sampleSize)
      {
        q.Enqueue(Randomness.NextSecureDouble());
      }
    
      for (int k = 0; k < 1000; k++)
      {
        // rotate
        q.Dequeue();
        q.Enqueue(Randomness.NextSecureDouble());
    
        var avg = q.Average();
    
        // Dividing by n−1 gives a better estimate of the population standard
        // deviation for the larger parent population than dividing by n, 
        // which gives a result which is correct for the sample only.
    
        var actual = Math.Sqrt(q.Sum(x => (x - avg) * (x - avg)) / (q.Count - 1));
    
        // see http://stats.stackexchange.com/a/1014/4576
    
        var expected = (q.Max() - q.Min()) / Math.Sqrt(12);
    
        Assert.AreEqual(expected, actual, errorMargin);
      }
    }
    

    【讨论】:

    • 我认为这应该是公认的答案,因为它与 Random 具有相同的行为,因为它永远不会返回 1.0。如果我错了,请纠正我……您可以稍微简化部分代码:v &amp;= ((1UL &lt;&lt; 53) - 1)v &gt;&gt; 11 相同,不是吗?
    • @Yellowfive 当然,但是v &amp;= ((1UL &lt;&lt; 53) - 1) 是一种用于屏蔽位的常见模式。我还没有看到使用位移位来屏蔽位。此外,该格式的结构中有 53 个,这将告诉您在完成掩码操作后,还剩下 53 个有效位的信息。这就是为什么我更喜欢这种掩蔽方法。
    【解决方案3】:

    您可以使用 BitConverter.ToDouble(...) 方法。它接受一个字节数组并将返回一个 Double。其他大多数基元类型都有相应的方法,以及从基元到字节数组的方法。

    【讨论】:

    • 对于双精度数(如 NaN),您需要避免一些特殊情况。
    • -0 乔恩是对的。更糟糕的是数字不在 0 和 1 之间。
    • 试试这个:BitConverter.GetBytes(double.NaN)。返回以 248、255 结尾的 8 字节数组。
    【解决方案4】:

    使用 BitConverter 将随机字节序列转换为 Double:

    byte[] random_bytes = new byte[8];  // BitConverter will expect an 8-byte array
    new RNGCryptoServiceProvider().GetBytes(random_bytes);
    
    double my_random_double = BitConverter.ToDouble(random_bytes, 0);
    

    【讨论】:

    • 查看我对 Michael 帖子的评论。可能导致 NaN、-Infinity 等。
    • 谢谢,但结果会非常不均匀,密度逐渐接近 0,没有机会得到 0.5 到 1 之间的任何值
    猜你喜欢
    • 1970-01-01
    • 2016-08-27
    • 2012-05-16
    • 2010-12-11
    • 2012-10-11
    • 1970-01-01
    相关资源
    最近更新 更多