从C#中0到1之间的随机字节数组中获取随机双(浮点)值?

时间:2017-04-21 23:01:31

标签: c# random

假设我有一个真正随机的字节数组(例如从熵源捕获)。

byte[] myTrulyRandomBytes = MyEntropyHardwareEngine.GetBytes(8);

现在,我希望获得一个随机的双精度浮点值,但是在0和正1之间的 (就像执行Random.NextDouble()函数一样)。

简单地将8个随机字节的数组传递给BitConverter.ToDouble()会产生奇怪的结果,但最重要的是,结果几乎不会小于1.

我对位操作很好,但浮点数的格式对我来说一直都很神秘。我尝试了很多位组合来应用随机性,并且最终总是发现数字要么刚刚超过1,要么非常接近0,要么非常大。

有人可以解释double中哪些位应该是随机的,以使其在0和1范围内随机?

5 个答案:

答案 0 :(得分:5)

这比你想象的容易;它完全是关于缩放(从0-1范围到其他范围时也是如此)。

基本上,如果你知道你有64个真正的随机位(8个字节),那么就这样做:

double zeroToOneDouble = (double)(BitConverter.ToUInt64(bytes) / (decimal)ulong.MaxValue);

这种算法的问题出现在你的"随机"比特实际上并不是随机的。当您需要专门的算法时,例如Mersenne Twister

答案 1 :(得分:5)

虽然已经给出了工作答案,但我会给另一个,看起来更糟,但不是:

long asLong = BitConverter.ToInt64(myTrulyRandomBytes, 0);
double number = (double)(asLong & long.MaxValue) / long.MaxValue;

ulong转换为double的问题在于它并非硬件直接支持,因此编译为:

 vxorps      xmm0,xmm0,xmm0 
 vcvtsi2sd   xmm0,xmm0,rcx   ; interpret ulong as long and convert it to double
 test        rcx,rcx         ; add fixup if it was "negative"
 jge         000000000000001D 
 vaddsd      xmm0,xmm0,mmword ptr [00000060h] 
 vdivsd      xmm0,xmm0,mmword ptr [00000068h] 

鉴于我的建议,它会更好地编译:

 vxorps      xmm0,xmm0,xmm0 
 vcvtsi2sd   xmm0,xmm0,rcx 
 vdivsd      xmm0,xmm0,mmword ptr [00000060h] 

两者都在.NET 4中使用x64 JIT进行了测试,但这一般适用于将ulong转换为double的好方法。

不要担心丢失熵的位数:首先在0.0和1.0之间只有2个 62 ,并且大多数较小的双打不能被选择,所以可能的结果数量更少。

请注意,这个以及所呈现的ulong示例可以精确地产生1.0并且在相邻结果之间分配具有稍微不同的间隙的值,因为它们不会除以2的幂。您可以将它们排除在1.0之外,并获得稍微均匀的间距(但请参见下面的第一个图,有一堆不同的间隙,但这种方式非常规则),如下所示:

long asLong = BitConverter.ToInt64(myTrulyRandomBytes, 0);
double number = (double)(asLong & long.MaxValue) / ((double)long.MaxValue + 1);

作为一个非常好的奖励,你现在可以将除法改为乘法(两个幂通常有倒数)

long asLong = BitConverter.ToInt64(myTrulyRandomBytes, 0);
double number = (double)(asLong & long.MaxValue) * 1.08420217248550443400745280086994171142578125E-19;

对于ulong来说,如果你真的想要使用它,那就相同了。

由于您似乎也对如何使用double - 位技巧感兴趣,我也可以证明这一点。

由于整个有效数/指数交易,它不能以超直接的方式完成(只需重新解释位和它的位),主要是因为选择指数均匀地解决了麻烦(使用一个统一的指数,由于大多数指数在那里,所以数字必然优先于0附近聚集。

但是,如果指数是固定的,那么在该区域内制作double均匀是很容易的。这不能是0比1,因为它涵盖了很多指数,但它可以是1到2然后我们可以减去1。

首先掩盖那些不会成为有效数字一部分的位:

x &= (1L << 52) - 1;

放入指数(1.0 - 2.0范围,不包括2)

x |= 0x3ff0000000000000;

重新解释并调整偏移量1:

return BitConverter.Int64BitsToDouble(x) - 1;

也应该很快。一个不幸的副作用是,这次它真的确实花费了一点熵,因为只有52但可能有53.这种方式总是留下最低有效位(隐含位窃取)一点)。

对分发有一些担忧,我现在将讨论。

选择随机(u)长并将其除以最大值的方法显然具有统一选择的(u)长,并且在此之后发生的事实际上是有趣的。结果可以合理地称为均匀分布,但是如果你把它看作一个离散的分布(它实际上是它),它看起来(定性地)像这样:(所有小流器的例子)

float distribution

忽略&#34;更厚的&#34;线条和更宽的间隙,这只是直方图很有趣。这些图使用2的幂除法,因此实际上没有间距问题,它只是奇怪地绘制。

Top是当你使用太多位时发生的事情,就像将一个完整的(u)long除以其最大值时所发生的那样。这使得较低的浮点数具有更好的分辨率,但是许多不同的(u)长度被映射到较高区域中的相同浮点数。如果你&#34;缩小&#34;这不一定是坏事。 密度在任何地方都是一样的。

当分辨率限制在最坏的情况(0.5到1.0区域)时,底部就会发生这种情况,你可以通过首先限制位数然后进行&#34;缩放整数来实现。应对。我的第二个建议是没有实现这一点,它只限于一半那个分辨率。

对于它的价值,NextDouble System.Random将非负int缩放到0.0 .. 1.0范围内。这个分辨率显然低于它可能的 lot 。它还使用不能int的{​​{1}}因此缩放大约1 /(2 31 -1)(不能用双精度表示,所以略微圆整),所以在相邻的可能结果之间实际上有33个略微不同的间隙,尽管大多数间隙是相同的距离。

由于int.MaxValue与现在可以强制使用的内容相比很小,因此您可以轻松生成int.MaxValue的所有可能结果并进行检查,例如我运行了这个:

NextDouble

答案 2 :(得分:3)

我不知道它是最好的解决方案,但它应该做的工作:

ulong asLong = BitConverter.ToUInt64(myTrulyRandomBytes, 0);
double number = (double)asLong / ulong.MaxValue;

我所做的就是将字节数组转换为ulong,然后将其除以它的最大值,以便结果在0和1之间。

答案 3 :(得分:2)

要确保long值在0到1的范围内,您可以应用以下掩码:

long longValue = BitConverter.ToInt64(myTrulyRandomBytes, 0);
longValue &= 0x3fefffffffffffff;

结果值保证在[0, 1)范围内 备注。 0x3fefffffffffffff值非常接近1,将打印为1,但实际上小于1。

如果要使生成的值更大,可以将指数的较高位设置为1。例如:

longValue |= 0x03c00000000000000;

总结:example on dotnetfiddle

答案 4 :(得分:1)

为您打印出来的简单代码。

for (double i = 0; i < 1.0; i+=0.05)
{
    var doubleToInt64Bits = BitConverter.DoubleToInt64Bits(i);
    Console.WriteLine("{0}:\t{1}", i, Convert.ToString(doubleToInt64Bits, 2));
}

0.05:   11111110101001100110011001100110011001100110011001100110011010
0.1:    11111110111001100110011001100110011001100110011001100110011010
0.15:   11111111000011001100110011001100110011001100110011001100110100
0.2:    11111111001001100110011001100110011001100110011001100110011010
0.25:   11111111010000000000000000000000000000000000000000000000000000
0.3:    11111111010011001100110011001100110011001100110011001100110011
0.35:   11111111010110011001100110011001100110011001100110011001100110
0.4:    11111111011001100110011001100110011001100110011001100110011001
0.45:   11111111011100110011001100110011001100110011001100110011001100
0.5:    11111111011111111111111111111111111111111111111111111111111111
0.55:   11111111100001100110011001100110011001100110011001100110011001
0.6:    11111111100011001100110011001100110011001100110011001100110011
0.65:   11111111100100110011001100110011001100110011001100110011001101
0.7:    11111111100110011001100110011001100110011001100110011001100111
0.75:   11111111101000000000000000000000000000000000000000000000000001
0.8:    11111111101001100110011001100110011001100110011001100110011011
0.85:   11111111101011001100110011001100110011001100110011001100110101
0.9:    11111111101100110011001100110011001100110011001100110011001111
0.95:   11111111101110011001100110011001100110011001100110011001101001